From 8e6406ae352c402ea4f65517bdb7d290d6c09b2f Mon Sep 17 00:00:00 2001 From: Chen Chengjun Date: Fri, 16 Jan 2026 02:19:05 +0000 Subject: [PATCH] Add regression tests --- test/initramfs/src/apps/Makefile | 1 + test/initramfs/src/apps/chroot/Makefile | 5 + test/initramfs/src/apps/chroot/chroot_jail.c | 160 +++++++++++++++++++ test/initramfs/src/apps/scripts/process.sh | 1 + 4 files changed, 167 insertions(+) create mode 100644 test/initramfs/src/apps/chroot/Makefile create mode 100644 test/initramfs/src/apps/chroot/chroot_jail.c diff --git a/test/initramfs/src/apps/Makefile b/test/initramfs/src/apps/Makefile index ba34f2e87..eef78efe3 100644 --- a/test/initramfs/src/apps/Makefile +++ b/test/initramfs/src/apps/Makefile @@ -15,6 +15,7 @@ TEST_BUILD_DIR ?= $(INITRAMFS)/test TEST_APPS := \ alarm \ capability \ + chroot \ clone3 \ cpu_affinity \ devfs \ diff --git a/test/initramfs/src/apps/chroot/Makefile b/test/initramfs/src/apps/chroot/Makefile new file mode 100644 index 000000000..c603a781a --- /dev/null +++ b/test/initramfs/src/apps/chroot/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MPL-2.0 + +include ../test_common.mk + +EXTRA_C_FLAGS := diff --git a/test/initramfs/src/apps/chroot/chroot_jail.c b/test/initramfs/src/apps/chroot/chroot_jail.c new file mode 100644 index 000000000..7531ed8e2 --- /dev/null +++ b/test/initramfs/src/apps/chroot/chroot_jail.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../test.h" + +FN_SETUP(create_chroot_env) +{ + // Create chroot target structure + CHECK_WITH(mkdir("/foo", 0755), _ret >= 0 || errno == EEXIST); + CHECK_WITH(mkdir("/foo/proc", 0755), _ret >= 0 || errno == EEXIST); + CHECK_WITH(mkdir("/foo/nix", 0755), _ret >= 0 || errno == EEXIST); + + // Perform bind mounts + CHECK(mount("proc", "/foo/proc", "proc", 0, "")); + CHECK(mount("/nix", "/foo/nix", NULL, MS_BIND, NULL)); +} +END_SETUP() + +// Helper function to check if a directory does NOT contain a specific entry +static int dir_not_contains(const char *path, const char *entry_name) +{ + DIR *dir = CHECK(opendir(path)); + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, entry_name) == 0) { + CHECK(closedir(dir)); + return -1; // Found the entry, which means failure + } + } + CHECK(closedir(dir)); + return 0; // Entry not found, success +} + +// Helper function to read a file and check for a substring +static int file_contains(const char *filepath, const char *substring) +{ + int fd = CHECK(open(filepath, O_RDONLY)); + char buf[4096] = { 0 }; + CHECK(read(fd, buf, sizeof(buf) - 1)); + CHECK(close(fd)); + return strstr(buf, substring) != NULL ? 0 : -1; +} + +// Macro to wait for a child process and check its exit status +#define WAIT_AND_CHECK_CHILD(pid) \ + do { \ + int status; \ + TEST_RES(waitpid(pid, &status, 0), \ + _ret == pid && WIFEXITED(status) && \ + WEXITSTATUS(status) == 0); \ + } while (0) + +FN_TEST(chroot_getcwd) +{ + pid_t pid = TEST_SUCC(fork()); + if (pid == 0) { + CHECK(chroot("/foo")); + CHECK(chdir("/")); + + char cwd[PATH_MAX]; + CHECK_WITH(getcwd(cwd, sizeof(cwd)), strcmp(cwd, "/") == 0); + + exit(EXIT_SUCCESS); + } else { + WAIT_AND_CHECK_CHILD(pid); + } +} +END_TEST() + +FN_TEST(chroot_chdir) +{ + pid_t pid = TEST_SUCC(fork()); + if (pid == 0) { + CHECK(chroot("/foo")); + CHECK(chdir("/")); + CHECK(chdir("..")); + + // Verify we can't see 'foo' directory + CHECK(dir_not_contains(".", "foo")); + + // Verify we are still at root + char cwd[PATH_MAX]; + CHECK_WITH(getcwd(cwd, sizeof(cwd)), strcmp(cwd, "/") == 0); + + exit(EXIT_SUCCESS); + } else { + WAIT_AND_CHECK_CHILD(pid); + } +} +END_TEST() + +FN_TEST(chroot_mountinfo) +{ + pid_t pid = TEST_SUCC(fork()); + if (pid == 0) { + CHECK(chroot("/foo")); + CHECK(chdir("/")); + + CHECK(file_contains("/proc/self/mountinfo", "/nix /nix")); + + exit(EXIT_SUCCESS); + } else { + WAIT_AND_CHECK_CHILD(pid); + } +} +END_TEST() + +FN_TEST(chroot_fd_escape) +{ + pid_t pid = TEST_SUCC(fork()); + if (pid == 0) { + int pre_chroot_fd = + CHECK(open("/test", O_RDONLY | O_DIRECTORY)); + + CHECK(chroot("/foo")); + CHECK(chdir("/")); + + // Use fchdir to go to the pre-chroot /test directory + CHECK(syscall(SYS_fchdir, pre_chroot_fd)); + + // Now getcwd should add "(unreachable)" prefix because we're outside the chroot jail + char cwd[PATH_MAX]; + CHECK_WITH(syscall(SYS_getcwd, cwd, sizeof(cwd)), + strcmp(cwd, "(unreachable)/test") == 0); + + // But we should be able to see 'foo' directory by listing parent + CHECK(chdir("..")); + CHECK_WITH(dir_not_contains(".", "foo"), _ret == -1); + + CHECK(close(pre_chroot_fd)); + exit(EXIT_SUCCESS); + } else { + WAIT_AND_CHECK_CHILD(pid); + } +} +END_TEST() + +FN_SETUP(cleanup_chroot_env) +{ + CHECK(umount("/foo/proc")); + CHECK(umount("/foo/nix")); + CHECK(rmdir("/foo/proc")); + CHECK(rmdir("/foo/nix")); + CHECK(rmdir("/foo")); +} +END_SETUP() \ No newline at end of file diff --git a/test/initramfs/src/apps/scripts/process.sh b/test/initramfs/src/apps/scripts/process.sh index adeb29332..c80ba61d5 100755 --- a/test/initramfs/src/apps/scripts/process.sh +++ b/test/initramfs/src/apps/scripts/process.sh @@ -10,6 +10,7 @@ cd ${SCRIPT_DIR}/.. echo "Start process test......" # These test programs are sorted by name. tests=" +chroot/chroot_jail clone3/clone_exit_signal clone3/clone_files clone3/clone_no_exit_signal