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