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 <[email protected]>
---
 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


Reply via email to