Add verification suites to test the kernel VFS and ELF loader $ORIGIN interpreter resolution. 1. Add a KUnit unit test 'exec_test_resolve_elf_interpreter()' verifying path resolution format logic. 2. Add a kselftests integration test containing: - A nolibc-based statically linked mock interpreter that prints a success message and returns 42. nolibc is used to bypass glibc's static startup code which segfaults when loaded as an interpreter due to AT_PHDR mismatches. - A dynamic test program configured to look for the interpreter at $ORIGIN/mock_interp. - A shell script harness checking for a PASS result. Assisted-by: Antigravity:Gemini-Pro Signed-off-by: Farid Zakaria --- fs/tests/exec_kunit.c | 26 +++++++++++++++++++ tools/testing/selftests/exec/Makefile | 12 ++++++--- tools/testing/selftests/exec/mock_interp.c | 6 +++++ tools/testing/selftests/exec/origin_interp.sh | 16 ++++++++++++ tools/testing/selftests/exec/test_prog.c | 5 ++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tools/testing/selftests/exec/mock_interp.c create mode 100755 tools/testing/selftests/exec/origin_interp.sh create mode 100644 tools/testing/selftests/exec/test_prog.c diff --git a/fs/tests/exec_kunit.c b/fs/tests/exec_kunit.c index 1c32cac09..991b9abad 100644 --- a/fs/tests/exec_kunit.c +++ b/fs/tests/exec_kunit.c @@ -119,8 +119,34 @@ static void exec_test_bprm_stack_limits(struct kunit *test) } } +static void exec_test_resolve_elf_interpreter(struct kunit *test) +{ + struct linux_binprm bprm = { .file = NULL }; + struct file *f; + char *resolved; + + // Test 1: Non-$ORIGIN interpreter path should just be duplicated + resolved = resolve_elf_interpreter(&bprm, "/lib64/ld-linux-x86-64.so.2"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, resolved); + KUNIT_EXPECT_STREQ(test, resolved, "/lib64/ld-linux-x86-64.so.2"); + kfree(resolved); + + // Test 2: $ORIGIN interpreter path + f = filp_open("/", O_RDONLY, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, f); + bprm.file = f; + + resolved = resolve_elf_interpreter(&bprm, "$ORIGIN/../lib/ld.so"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, resolved); + KUNIT_EXPECT_STREQ(test, resolved, "//../lib/ld.so"); + kfree(resolved); + + filp_close(f, NULL); +} + static struct kunit_case exec_test_cases[] = { KUNIT_CASE(exec_test_bprm_stack_limits), + KUNIT_CASE(exec_test_resolve_elf_interpreter), {}, }; diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile index 45a3cfc43..5e2e305cb 100644 --- a/tools/testing/selftests/exec/Makefile +++ b/tools/testing/selftests/exec/Makefile @@ -10,9 +10,9 @@ ALIGN_PIES := $(patsubst %,load_address.%,$(ALIGNS)) ALIGN_STATIC_PIES := $(patsubst %,load_address.static.%,$(ALIGNS)) ALIGNMENT_TESTS := $(ALIGN_PIES) $(ALIGN_STATIC_PIES) -TEST_PROGS := binfmt_script.py check-exec-tests.sh -TEST_GEN_PROGS := execveat non-regular $(ALIGNMENT_TESTS) -TEST_GEN_PROGS_EXTENDED := false inc set-exec script-exec.inc script-noexec.inc +TEST_PROGS := binfmt_script.py check-exec-tests.sh origin_interp.sh +TEST_GEN_PROGS := execveat non-regular $(ALIGNMENT_TESTS) test_prog +TEST_GEN_PROGS_EXTENDED := false inc set-exec script-exec.inc script-noexec.inc mock_interp TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir # Makefile is a run-time dependency, since it's accessed by the execveat test TEST_FILES := Makefile @@ -55,3 +55,9 @@ $(OUTPUT)/script-exec.inc: $(CHECK_EXEC_SAMPLES)/script-exec.inc cp $< $@ $(OUTPUT)/script-noexec.inc: $(CHECK_EXEC_SAMPLES)/script-noexec.inc cp $< $@ + +$(OUTPUT)/mock_interp: mock_interp.c + $(CC) $(CFLAGS) $(LDFLAGS) -static -nostdlib -include ../../../include/nolibc/nolibc.h $< -o $@ + +$(OUTPUT)/test_prog: test_prog.c $(OUTPUT)/mock_interp + $(CC) $(CFLAGS) $(LDFLAGS) -Wl,-dynamic-linker,'$$ORIGIN/mock_interp' $< -o $@ diff --git a/tools/testing/selftests/exec/mock_interp.c b/tools/testing/selftests/exec/mock_interp.c new file mode 100644 index 000000000..9c9ca1098 --- /dev/null +++ b/tools/testing/selftests/exec/mock_interp.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +int main(void) +{ + write(1, "Hello from mock interpreter!\n", 29); + return 42; +} diff --git a/tools/testing/selftests/exec/origin_interp.sh b/tools/testing/selftests/exec/origin_interp.sh new file mode 100755 index 000000000..635a40839 --- /dev/null +++ b/tools/testing/selftests/exec/origin_interp.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +# Execute the test program which has its interpreter set to $ORIGIN/mock_interp +# Note that mock_interp must be in the same directory. +dir=$(dirname "$0") +out=$("$dir"/test_prog 2>&1) +exit_code=$? + +if [ $exit_code -eq 42 ] && [ "$out" = "Hello from mock interpreter!" ]; then + echo "origin_interp: PASS" + exit 0 +else + echo "origin_interp: FAIL (exit_code=$exit_code, output='$out')" + exit 1 +fi diff --git a/tools/testing/selftests/exec/test_prog.c b/tools/testing/selftests/exec/test_prog.c new file mode 100644 index 000000000..451614def --- /dev/null +++ b/tools/testing/selftests/exec/test_prog.c @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +int main(void) +{ + return 0; /* Should never be reached if interpreter is loaded instead */ +} -- 2.51.2