mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-01 00:58:39 +02:00 
			
		
		
		
	samples/check-exec: Add an enlighten "inc" interpreter and 28 tests
Add a very simple script interpreter called "inc" that can evaluate two
different commands (one per line):
- "?" to initialize a counter from user's input;
- "+" to increment the counter (which is set to 0 by default).
It is enlighten to only interpret executable files according to
AT_EXECVE_CHECK and the related securebits:
  # Executing a script with RESTRICT_FILE is only allowed if the script
  # is executable:
  ./set-exec -f -- ./inc script-exec.inc # Allowed
  ./set-exec -f -- ./inc script-noexec.inc # Denied
  # Executing stdin with DENY_INTERACTIVE is only allowed if stdin is an
  # executable regular file:
  ./set-exec -i -- ./inc -i < script-exec.inc # Allowed
  ./set-exec -i -- ./inc -i < script-noexec.inc # Denied
  # However, a pipe is not executable and it is then denied:
  cat script-noexec.inc | ./set-exec -i -- ./inc -i # Denied
  # Executing raw data (e.g. command argument) with DENY_INTERACTIVE is
  # always denied.
  ./set-exec -i -- ./inc -c "+" # Denied
  ./inc -c "$(<script-ask.inc)" # Allowed
  # To directly execute a script, we can update $PATH (used by `env`):
  PATH="${PATH}:." ./script-exec.inc
  # To execute several commands passed as argument:
Add a complete test suite to check the script interpreter against all
possible execution cases:
  make TARGETS=exec kselftest-install
  ./tools/testing/selftests/kselftest_install/run_kselftest.sh
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge Hallyn <serge@hallyn.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241212174223.389435-8-mic@digikod.net
Signed-off-by: Kees Cook <kees@kernel.org>
			
			
This commit is contained in:
		
							parent
							
								
									3e707b07f5
								
							
						
					
					
						commit
						2a69962be4
					
				
					 11 changed files with 451 additions and 3 deletions
				
			
		|  | @ -296,7 +296,9 @@ config SAMPLE_CHECK_EXEC | ||||||
| 	depends on CC_CAN_LINK && HEADERS_INSTALL | 	depends on CC_CAN_LINK && HEADERS_INSTALL | ||||||
| 	help | 	help | ||||||
| 	  Build a tool to easily configure SECBIT_EXEC_RESTRICT_FILE and | 	  Build a tool to easily configure SECBIT_EXEC_RESTRICT_FILE and | ||||||
| 	  SECBIT_EXEC_DENY_INTERACTIVE. | 	  SECBIT_EXEC_DENY_INTERACTIVE, and a simple script interpreter to | ||||||
|  | 	  demonstrate how they should be used with execveat(2) + | ||||||
|  | 	  AT_EXECVE_CHECK. | ||||||
| 
 | 
 | ||||||
| source "samples/rust/Kconfig" | source "samples/rust/Kconfig" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								samples/check-exec/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								samples/check-exec/.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -1 +1,2 @@ | ||||||
|  | /inc | ||||||
| /set-exec | /set-exec | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| # SPDX-License-Identifier: BSD-3-Clause
 | # SPDX-License-Identifier: BSD-3-Clause
 | ||||||
| 
 | 
 | ||||||
| userprogs-always-y := \
 | userprogs-always-y := \
 | ||||||
|  | 	inc \
 | ||||||
| 	set-exec | 	set-exec | ||||||
| 
 | 
 | ||||||
| userccflags += -I usr/include | userccflags += -I usr/include | ||||||
|  |  | ||||||
							
								
								
									
										205
									
								
								samples/check-exec/inc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								samples/check-exec/inc.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,205 @@ | ||||||
|  | // SPDX-License-Identifier: BSD-3-Clause
 | ||||||
|  | /*
 | ||||||
|  |  * Very simple script interpreter that can evaluate two different commands (one | ||||||
|  |  * per line): | ||||||
|  |  * - "?" to initialize a counter from user's input; | ||||||
|  |  * - "+" to increment the counter (which is set to 0 by default). | ||||||
|  |  * | ||||||
|  |  * See tools/testing/selftests/exec/check-exec-tests.sh and | ||||||
|  |  * Documentation/userspace-api/check_exec.rst | ||||||
|  |  * | ||||||
|  |  * Copyright © 2024 Microsoft Corporation | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #define _GNU_SOURCE | ||||||
|  | #include <errno.h> | ||||||
|  | #include <linux/fcntl.h> | ||||||
|  | #include <linux/prctl.h> | ||||||
|  | #include <linux/securebits.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <sys/prctl.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | 
 | ||||||
|  | /* Returns 1 on error, 0 otherwise. */ | ||||||
|  | static int interpret_buffer(char *buffer, size_t buffer_size) | ||||||
|  | { | ||||||
|  | 	char *line, *saveptr = NULL; | ||||||
|  | 	long long number = 0; | ||||||
|  | 
 | ||||||
|  | 	/* Each command is the first character of a line. */ | ||||||
|  | 	saveptr = NULL; | ||||||
|  | 	line = strtok_r(buffer, "\n", &saveptr); | ||||||
|  | 	while (line) { | ||||||
|  | 		if (*line != '#' && strlen(line) != 1) { | ||||||
|  | 			fprintf(stderr, "# ERROR: Unknown string\n"); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 		switch (*line) { | ||||||
|  | 		case '#': | ||||||
|  | 			/* Skips shebang and comments. */ | ||||||
|  | 			break; | ||||||
|  | 		case '+': | ||||||
|  | 			/* Increments and prints the number. */ | ||||||
|  | 			number++; | ||||||
|  | 			printf("%lld\n", number); | ||||||
|  | 			break; | ||||||
|  | 		case '?': | ||||||
|  | 			/* Reads integer from stdin. */ | ||||||
|  | 			fprintf(stderr, "> Enter new number: \n"); | ||||||
|  | 			if (scanf("%lld", &number) != 1) { | ||||||
|  | 				fprintf(stderr, | ||||||
|  | 					"# WARNING: Failed to read number from stdin\n"); | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			fprintf(stderr, "# ERROR: Unknown character '%c'\n", | ||||||
|  | 				*line); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 		line = strtok_r(NULL, "\n", &saveptr); | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Returns 1 on error, 0 otherwise. */ | ||||||
|  | static int interpret_stream(FILE *script, char *const script_name, | ||||||
|  | 			    char *const *const envp, const bool restrict_stream) | ||||||
|  | { | ||||||
|  | 	int err; | ||||||
|  | 	char *const script_argv[] = { script_name, NULL }; | ||||||
|  | 	char buf[128] = {}; | ||||||
|  | 	size_t buf_size = sizeof(buf); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * We pass a valid argv and envp to the kernel to emulate a native | ||||||
|  | 	 * script execution.  We must use the script file descriptor instead of | ||||||
|  | 	 * the script path name to avoid race conditions. | ||||||
|  | 	 */ | ||||||
|  | 	err = execveat(fileno(script), "", script_argv, envp, | ||||||
|  | 		       AT_EMPTY_PATH | AT_EXECVE_CHECK); | ||||||
|  | 	if (err && restrict_stream) { | ||||||
|  | 		perror("ERROR: Script execution check"); | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Reads script. */ | ||||||
|  | 	buf_size = fread(buf, 1, buf_size - 1, script); | ||||||
|  | 	return interpret_buffer(buf, buf_size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void print_usage(const char *argv0) | ||||||
|  | { | ||||||
|  | 	fprintf(stderr, "usage: %s <script.inc> | -i | -c <command>\n\n", | ||||||
|  | 		argv0); | ||||||
|  | 	fprintf(stderr, "Example:\n"); | ||||||
|  | 	fprintf(stderr, "  ./set-exec -fi -- ./inc -i < script-exec.inc\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main(const int argc, char *const argv[], char *const *const envp) | ||||||
|  | { | ||||||
|  | 	int opt; | ||||||
|  | 	char *cmd = NULL; | ||||||
|  | 	char *script_name = NULL; | ||||||
|  | 	bool interpret_stdin = false; | ||||||
|  | 	FILE *script_file = NULL; | ||||||
|  | 	int secbits; | ||||||
|  | 	bool deny_interactive, restrict_file; | ||||||
|  | 	size_t arg_nb; | ||||||
|  | 
 | ||||||
|  | 	secbits = prctl(PR_GET_SECUREBITS); | ||||||
|  | 	if (secbits == -1) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * This should never happen, except with a buggy seccomp | ||||||
|  | 		 * filter. | ||||||
|  | 		 */ | ||||||
|  | 		perror("ERROR: Failed to get securebits"); | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	deny_interactive = !!(secbits & SECBIT_EXEC_DENY_INTERACTIVE); | ||||||
|  | 	restrict_file = !!(secbits & SECBIT_EXEC_RESTRICT_FILE); | ||||||
|  | 
 | ||||||
|  | 	while ((opt = getopt(argc, argv, "c:i")) != -1) { | ||||||
|  | 		switch (opt) { | ||||||
|  | 		case 'c': | ||||||
|  | 			if (cmd) { | ||||||
|  | 				fprintf(stderr, "ERROR: Command already set"); | ||||||
|  | 				return 1; | ||||||
|  | 			} | ||||||
|  | 			cmd = optarg; | ||||||
|  | 			break; | ||||||
|  | 		case 'i': | ||||||
|  | 			interpret_stdin = true; | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			print_usage(argv[0]); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Checks that only one argument is used, or read stdin. */ | ||||||
|  | 	arg_nb = !!cmd + !!interpret_stdin; | ||||||
|  | 	if (arg_nb == 0 && argc == 2) { | ||||||
|  | 		script_name = argv[1]; | ||||||
|  | 	} else if (arg_nb != 1) { | ||||||
|  | 		print_usage(argv[0]); | ||||||
|  | 		return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (cmd) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * Other kind of interactive interpretations should be denied | ||||||
|  | 		 * as well (e.g. CLI arguments passing script snippets, | ||||||
|  | 		 * environment variables interpreted as script).  However, any | ||||||
|  | 		 * way to pass script files should only be restricted according | ||||||
|  | 		 * to restrict_file. | ||||||
|  | 		 */ | ||||||
|  | 		if (deny_interactive) { | ||||||
|  | 			fprintf(stderr, | ||||||
|  | 				"ERROR: Interactive interpretation denied.\n"); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return interpret_buffer(cmd, strlen(cmd)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (interpret_stdin && !script_name) { | ||||||
|  | 		script_file = stdin; | ||||||
|  | 		/*
 | ||||||
|  | 		 * As for any execve(2) call, this path may be logged by the | ||||||
|  | 		 * kernel. | ||||||
|  | 		 */ | ||||||
|  | 		script_name = "/proc/self/fd/0"; | ||||||
|  | 		/*
 | ||||||
|  | 		 * When stdin is used, it can point to a regular file or a | ||||||
|  | 		 * pipe.  Restrict stdin execution according to | ||||||
|  | 		 * SECBIT_EXEC_DENY_INTERACTIVE but always allow executable | ||||||
|  | 		 * files (which are not considered as interactive inputs). | ||||||
|  | 		 */ | ||||||
|  | 		return interpret_stream(script_file, script_name, envp, | ||||||
|  | 					deny_interactive); | ||||||
|  | 	} else if (script_name && !interpret_stdin) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * In this sample, we don't pass any argument to scripts, but | ||||||
|  | 		 * otherwise we would have to forge an argv with such | ||||||
|  | 		 * arguments. | ||||||
|  | 		 */ | ||||||
|  | 		script_file = fopen(script_name, "r"); | ||||||
|  | 		if (!script_file) { | ||||||
|  | 			perror("ERROR: Failed to open script"); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 		/*
 | ||||||
|  | 		 * Restricts file execution according to | ||||||
|  | 		 * SECBIT_EXEC_RESTRICT_FILE. | ||||||
|  | 		 */ | ||||||
|  | 		return interpret_stream(script_file, script_name, envp, | ||||||
|  | 					restrict_file); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	print_usage(argv[0]); | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								samples/check-exec/run-script-ask.inc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								samples/check-exec/run-script-ask.inc
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | #!/usr/bin/env sh
 | ||||||
|  | # SPDX-License-Identifier: BSD-3-Clause
 | ||||||
|  | 
 | ||||||
|  | DIR="$(dirname -- "$0")" | ||||||
|  | 
 | ||||||
|  | PATH="${PATH}:${DIR}" | ||||||
|  | 
 | ||||||
|  | set -x | ||||||
|  | "${DIR}/script-ask.inc" | ||||||
							
								
								
									
										5
									
								
								samples/check-exec/script-ask.inc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								samples/check-exec/script-ask.inc
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | #!/usr/bin/env inc
 | ||||||
|  | # SPDX-License-Identifier: BSD-3-Clause
 | ||||||
|  | 
 | ||||||
|  | ? | ||||||
|  | + | ||||||
							
								
								
									
										4
									
								
								samples/check-exec/script-exec.inc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								samples/check-exec/script-exec.inc
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | #!/usr/bin/env inc
 | ||||||
|  | # SPDX-License-Identifier: BSD-3-Clause
 | ||||||
|  | 
 | ||||||
|  | + | ||||||
							
								
								
									
										4
									
								
								samples/check-exec/script-noexec.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								samples/check-exec/script-noexec.inc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | #!/usr/bin/env inc
 | ||||||
|  | # SPDX-License-Identifier: BSD-3-Clause
 | ||||||
|  | 
 | ||||||
|  | + | ||||||
							
								
								
									
										2
									
								
								tools/testing/selftests/exec/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								tools/testing/selftests/exec/.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -11,9 +11,11 @@ non-regular | ||||||
| null-argv | null-argv | ||||||
| /check-exec | /check-exec | ||||||
| /false | /false | ||||||
|  | /inc | ||||||
| /load_address.* | /load_address.* | ||||||
| !load_address.c | !load_address.c | ||||||
| /recursion-depth | /recursion-depth | ||||||
|  | /set-exec | ||||||
| xxxxxxxx* | xxxxxxxx* | ||||||
| pipe | pipe | ||||||
| S_I*.test | S_I*.test | ||||||
|  |  | ||||||
|  | @ -10,9 +10,9 @@ ALIGN_PIES        := $(patsubst %,load_address.%,$(ALIGNS)) | ||||||
| ALIGN_STATIC_PIES := $(patsubst %,load_address.static.%,$(ALIGNS)) | ALIGN_STATIC_PIES := $(patsubst %,load_address.static.%,$(ALIGNS)) | ||||||
| ALIGNMENT_TESTS   := $(ALIGN_PIES) $(ALIGN_STATIC_PIES) | ALIGNMENT_TESTS   := $(ALIGN_PIES) $(ALIGN_STATIC_PIES) | ||||||
| 
 | 
 | ||||||
| TEST_PROGS := binfmt_script.py | TEST_PROGS := binfmt_script.py check-exec-tests.sh | ||||||
| TEST_GEN_PROGS := execveat non-regular $(ALIGNMENT_TESTS) | TEST_GEN_PROGS := execveat non-regular $(ALIGNMENT_TESTS) | ||||||
| TEST_GEN_PROGS_EXTENDED := false | TEST_GEN_PROGS_EXTENDED := false inc set-exec script-exec.inc script-noexec.inc | ||||||
| TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir | TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir | ||||||
| # Makefile is a run-time dependency, since it's accessed by the execveat test
 | # Makefile is a run-time dependency, since it's accessed by the execveat test
 | ||||||
| TEST_FILES := Makefile | TEST_FILES := Makefile | ||||||
|  | @ -26,6 +26,8 @@ EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* | ||||||
| 
 | 
 | ||||||
| include ../lib.mk | include ../lib.mk | ||||||
| 
 | 
 | ||||||
|  | CHECK_EXEC_SAMPLES := $(top_srcdir)/samples/check-exec | ||||||
|  | 
 | ||||||
| $(OUTPUT)/subdir: | $(OUTPUT)/subdir: | ||||||
| 	mkdir -p $@ | 	mkdir -p $@ | ||||||
| $(OUTPUT)/script: Makefile | $(OUTPUT)/script: Makefile | ||||||
|  | @ -45,3 +47,11 @@ $(OUTPUT)/load_address.static.0x%: load_address.c | ||||||
| 		-fPIE -static-pie $< -o $@ | 		-fPIE -static-pie $< -o $@ | ||||||
| $(OUTPUT)/false: false.c | $(OUTPUT)/false: false.c | ||||||
| 	$(CC) $(CFLAGS) $(LDFLAGS) -static $< -o $@ | 	$(CC) $(CFLAGS) $(LDFLAGS) -static $< -o $@ | ||||||
|  | $(OUTPUT)/inc: $(CHECK_EXEC_SAMPLES)/inc.c | ||||||
|  | 	$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ | ||||||
|  | $(OUTPUT)/set-exec: $(CHECK_EXEC_SAMPLES)/set-exec.c | ||||||
|  | 	$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ | ||||||
|  | $(OUTPUT)/script-exec.inc: $(CHECK_EXEC_SAMPLES)/script-exec.inc | ||||||
|  | 	cp $< $@ | ||||||
|  | $(OUTPUT)/script-noexec.inc: $(CHECK_EXEC_SAMPLES)/script-noexec.inc | ||||||
|  | 	cp $< $@ | ||||||
|  |  | ||||||
							
								
								
									
										205
									
								
								tools/testing/selftests/exec/check-exec-tests.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										205
									
								
								tools/testing/selftests/exec/check-exec-tests.sh
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,205 @@ | ||||||
|  | #!/usr/bin/env bash | ||||||
|  | # SPDX-License-Identifier: GPL-2.0 | ||||||
|  | # | ||||||
|  | # Test the "inc" interpreter. | ||||||
|  | # | ||||||
|  | # See include/uapi/linux/securebits.h, include/uapi/linux/fcntl.h and | ||||||
|  | # samples/check-exec/inc.c | ||||||
|  | # | ||||||
|  | # Copyright © 2024 Microsoft Corporation | ||||||
|  | 
 | ||||||
|  | set -u -e -o pipefail | ||||||
|  | 
 | ||||||
|  | EXPECTED_OUTPUT="1" | ||||||
|  | exec 2>/dev/null | ||||||
|  | 
 | ||||||
|  | DIR="$(dirname $(readlink -f "$0"))" | ||||||
|  | source "${DIR}"/../kselftest/ktap_helpers.sh | ||||||
|  | 
 | ||||||
|  | exec_direct() { | ||||||
|  | 	local expect="$1" | ||||||
|  | 	local script="$2" | ||||||
|  | 	shift 2 | ||||||
|  | 	local ret=0 | ||||||
|  | 	local out | ||||||
|  | 
 | ||||||
|  | 	# Updates PATH for `env` to execute the `inc` interpreter. | ||||||
|  | 	out="$(PATH="." "$@" "${script}")" || ret=$? | ||||||
|  | 
 | ||||||
|  | 	if [[ ${ret} -ne ${expect} ]]; then | ||||||
|  | 		echo "ERROR: Wrong expectation for direct file execution: ${ret}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | 	if [[ ${ret} -eq 0 && "${out}" != "${EXPECTED_OUTPUT}" ]]; then | ||||||
|  | 		echo "ERROR: Wrong output for direct file execution: ${out}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | exec_indirect() { | ||||||
|  | 	local expect="$1" | ||||||
|  | 	local script="$2" | ||||||
|  | 	shift 2 | ||||||
|  | 	local ret=0 | ||||||
|  | 	local out | ||||||
|  | 
 | ||||||
|  | 	# Script passed as argument. | ||||||
|  | 	out="$("$@" ./inc "${script}")" || ret=$? | ||||||
|  | 
 | ||||||
|  | 	if [[ ${ret} -ne ${expect} ]]; then | ||||||
|  | 		echo "ERROR: Wrong expectation for indirect file execution: ${ret}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | 	if [[ ${ret} -eq 0 && "${out}" != "${EXPECTED_OUTPUT}" ]]; then | ||||||
|  | 		echo "ERROR: Wrong output for indirect file execution: ${out}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | exec_stdin_reg() { | ||||||
|  | 	local expect="$1" | ||||||
|  | 	local script="$2" | ||||||
|  | 	shift 2 | ||||||
|  | 	local ret=0 | ||||||
|  | 	local out | ||||||
|  | 
 | ||||||
|  | 	# Executing stdin must be allowed if the related file is executable. | ||||||
|  | 	out="$("$@" ./inc -i < "${script}")" || ret=$? | ||||||
|  | 
 | ||||||
|  | 	if [[ ${ret} -ne ${expect} ]]; then | ||||||
|  | 		echo "ERROR: Wrong expectation for stdin regular file execution: ${ret}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | 	if [[ ${ret} -eq 0 && "${out}" != "${EXPECTED_OUTPUT}" ]]; then | ||||||
|  | 		echo "ERROR: Wrong output for stdin regular file execution: ${out}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | exec_stdin_pipe() { | ||||||
|  | 	local expect="$1" | ||||||
|  | 	shift | ||||||
|  | 	local ret=0 | ||||||
|  | 	local out | ||||||
|  | 
 | ||||||
|  | 	# A pipe is not executable. | ||||||
|  | 	out="$(cat script-exec.inc | "$@" ./inc -i)" || ret=$? | ||||||
|  | 
 | ||||||
|  | 	if [[ ${ret} -ne ${expect} ]]; then | ||||||
|  | 		echo "ERROR: Wrong expectation for stdin pipe execution: ${ret}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | exec_argument() { | ||||||
|  | 	local expect="$1" | ||||||
|  | 	local ret=0 | ||||||
|  | 	shift | ||||||
|  | 	local out | ||||||
|  | 
 | ||||||
|  | 	# Script not coming from a file must not be executed. | ||||||
|  | 	out="$("$@" ./inc -c "$(< script-exec.inc)")" || ret=$? | ||||||
|  | 
 | ||||||
|  | 	if [[ ${ret} -ne ${expect} ]]; then | ||||||
|  | 		echo "ERROR: Wrong expectation for arbitrary argument execution: ${ret}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | 	if [[ ${ret} -eq 0 && "${out}" != "${EXPECTED_OUTPUT}" ]]; then | ||||||
|  | 		echo "ERROR: Wrong output for arbitrary argument execution: ${out}" | ||||||
|  | 		return 1 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | exec_interactive() { | ||||||
|  | 	exec_stdin_pipe "$@" | ||||||
|  | 	exec_argument "$@" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ktap_test() { | ||||||
|  | 	ktap_test_result "$*" "$@" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ktap_print_header | ||||||
|  | ktap_set_plan 28 | ||||||
|  | 
 | ||||||
|  | # Without secbit configuration, nothing is changed. | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "By default, executable scripts are allowed to be interpreted and executed." | ||||||
|  | ktap_test exec_direct 0 script-exec.inc | ||||||
|  | ktap_test exec_indirect 0 script-exec.inc | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "By default, executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-exec.inc | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "By default, non-executable scripts are allowed to be interpreted, but not directly executed." | ||||||
|  | # We get 126 because of direct execution by Bash. | ||||||
|  | ktap_test exec_direct 126 script-noexec.inc | ||||||
|  | ktap_test exec_indirect 0 script-noexec.inc | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "By default, non-executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-noexec.inc | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "By default, interactive commands are allowed to be interpreted." | ||||||
|  | ktap_test exec_interactive 0 | ||||||
|  | 
 | ||||||
|  | # With only file restriction: protect non-malicious users from inadvertent errors (e.g. python ~/Downloads/*.py). | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -f, executable scripts are allowed to be interpreted and executed." | ||||||
|  | ktap_test exec_direct 0 script-exec.inc ./set-exec -f -- | ||||||
|  | ktap_test exec_indirect 0 script-exec.inc ./set-exec -f -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -f, executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-exec.inc ./set-exec -f -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -f, non-executable scripts are not allowed to be executed nor interpreted." | ||||||
|  | # Direct execution of non-executable script is alwayse denied by the kernel. | ||||||
|  | ktap_test exec_direct 1 script-noexec.inc ./set-exec -f -- | ||||||
|  | ktap_test exec_indirect 1 script-noexec.inc ./set-exec -f -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -f, non-executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-noexec.inc ./set-exec -f -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -f, interactive commands are allowed to be interpreted." | ||||||
|  | ktap_test exec_interactive 0 ./set-exec -f -- | ||||||
|  | 
 | ||||||
|  | # With only denied interactive commands: check or monitor script content (e.g. with LSM). | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -i, executable scripts are allowed to be interpreted and executed." | ||||||
|  | ktap_test exec_direct 0 script-exec.inc ./set-exec -i -- | ||||||
|  | ktap_test exec_indirect 0 script-exec.inc ./set-exec -i -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -i, executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-exec.inc ./set-exec -i -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -i, non-executable scripts are allowed to be interpreted, but not directly executed." | ||||||
|  | # Direct execution of non-executable script is alwayse denied by the kernel. | ||||||
|  | ktap_test exec_direct 1 script-noexec.inc ./set-exec -i -- | ||||||
|  | ktap_test exec_indirect 0 script-noexec.inc ./set-exec -i -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -i, non-executable stdin is not allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 1 script-noexec.inc ./set-exec -i -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -i, interactive commands are not allowed to be interpreted." | ||||||
|  | ktap_test exec_interactive 1 ./set-exec -i -- | ||||||
|  | 
 | ||||||
|  | # With both file restriction and denied interactive commands: only allow executable scripts. | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -fi, executable scripts are allowed to be interpreted and executed." | ||||||
|  | ktap_test exec_direct 0 script-exec.inc ./set-exec -fi -- | ||||||
|  | ktap_test exec_indirect 0 script-exec.inc ./set-exec -fi -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -fi, executable stdin is allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 0 script-exec.inc ./set-exec -fi -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -fi, non-executable scripts are not allowed to be interpreted nor executed." | ||||||
|  | # Direct execution of non-executable script is alwayse denied by the kernel. | ||||||
|  | ktap_test exec_direct 1 script-noexec.inc ./set-exec -fi -- | ||||||
|  | ktap_test exec_indirect 1 script-noexec.inc ./set-exec -fi -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -fi, non-executable stdin is not allowed to be interpreted." | ||||||
|  | ktap_test exec_stdin_reg 1 script-noexec.inc ./set-exec -fi -- | ||||||
|  | 
 | ||||||
|  | ktap_print_msg "With -fi, interactive commands are not allowed to be interpreted." | ||||||
|  | ktap_test exec_interactive 1 ./set-exec -fi -- | ||||||
|  | 
 | ||||||
|  | ktap_finished | ||||||
		Loading…
	
		Reference in a new issue
	
	 Mickaël Salaün
						Mickaël Salaün