diff --git a/test/initramfs/src/apps/namespace/proc_nsfs.c b/test/initramfs/src/apps/namespace/proc_nsfs.c new file mode 100644 index 000000000..0a988c07f --- /dev/null +++ b/test/initramfs/src/apps/namespace/proc_nsfs.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../test.h" + +#define NS_DIR "/proc/self/ns" + +// ns_names contains the namespace names as they appear in readlink output. +// For pid_for_children and time_for_children, the readlink output shows "pid" and "time" respectively. +// For other namespaces, ns_files and ns_names have identical corresponding strings. +char *ns_files[] = { "uts", "mnt", "user" }; +char *ns_names[] = { "uts", "mnt", "user" }; +int clone_flags[] = { CLONE_NEWUTS, CLONE_NEWNS, CLONE_NEWUSER }; + +FN_TEST(common_fs_operations) +{ + char path[PATH_MAX]; + char buf[1]; + size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]); + + for (size_t i = 0; i < ns_count; i++) { + memset(path, 0, sizeof(path)); + snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]); + + TEST_ERRNO(open(path, O_RDWR), EPERM); + TEST_ERRNO(open(path, O_WRONLY), EPERM); + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + + TEST_ERRNO(read(nsfd, buf, 1), EINVAL); + TEST_ERRNO(write(nsfd, buf, 1), EBADF); + + struct pollfd ns_pfd = { .fd = nsfd, + .events = POLLIN | POLLOUT | + POLLRDHUP | POLLPRI | + POLLRDNORM }; + TEST_RES(poll(&ns_pfd, 1, -1), + ns_pfd.revents == (POLLIN | POLLOUT | POLLRDNORM)); + + struct stat64 stat; + TEST_RES(fstat64(nsfd, &stat), + stat.st_mode == (0444 | S_IFREG)); + + TEST_ERRNO(lseek64(nsfd, 0, SEEK_SET), ESPIPE); + } +} +END_TEST() + +FN_TEST(readlink) +{ + char path[PATH_MAX]; + char buf[256]; + size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]); + + for (size_t i = 0; i < ns_count; i++) { + memset(path, 0, sizeof(path)); + memset(buf, 0, sizeof(buf)); + snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]); + + char expected_format[256] = { 0 }; + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + struct stat stat; + TEST_SUCC(fstat(nsfd, &stat)); + char *ns_name = ns_names[i]; + snprintf(expected_format, sizeof(expected_format), "%s:[%u]", + ns_name, (unsigned int)stat.st_ino); + + TEST_RES(readlink(path, buf, sizeof(buf) - 1), + strcmp(expected_format, buf) == 0); + } +} +END_TEST() + +FN_TEST(zombie_process) +{ + char path[PATH_MAX]; + size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]); + + pid_t pid = fork(); + TEST_RES(pid >= 0, 1); + + if (pid == 0) { + // Child process exits immediately to become zombie + exit(0); + } + + TEST_SUCC(waitid(P_PID, pid, NULL, WNOWAIT | WEXITED)); + + // Try to access zombie process's namespace files + for (size_t i = 0; i < ns_count; i++) { + char *ns_file = ns_files[i]; + snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, + ns_files[i]); + + // Should still be able to open zombie's PID/user namespace files + if (strcmp(ns_file, "pid") == 0 || + strcmp(ns_file, "user") == 0) { + char buf[256] = { 0 }; + TEST_SUCC(readlink(path, buf, sizeof(buf) - 1)); + + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + TEST_SUCC(close(nsfd)); + } else { + TEST_ERRNO(open(path, O_RDONLY), ENOENT); + } + } + + TEST_SUCC(waitpid(pid, NULL, 0)); +} +END_TEST() + +FN_TEST(ioctl) +{ + char path[PATH_MAX]; + + for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) { + snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]); + + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + // NS_GET_USERNS + if (strcmp(ns_files[i], "user") != 0) { + int userns_fd = TEST_SUCC(ioctl(nsfd, NS_GET_USERNS)); + TEST_SUCC(close(userns_fd)); + } else { + TEST_ERRNO(ioctl(nsfd, NS_GET_USERNS), EPERM); + } + + // NS_GET_PARENT - get parent namespace (should fail for non-hierarchical ns) + if (strcmp(ns_files[i], "user") != 0) { + TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EINVAL); + } else { + TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EPERM); + } + + // NS_GET_NSTYPE + TEST_RES(ioctl(nsfd, NS_GET_NSTYPE), _ret == clone_flags[i]); + + // NS_GET_OWNER_UID: User namespace only + if (strcmp(ns_files[i], "user") != 0) { + TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID), EINVAL); + } else { + TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID, 0), EFAULT); + uid_t uid; + TEST_SUCC(ioctl(nsfd, NS_GET_OWNER_UID, &uid)); + } + } +} +END_TEST() + +FN_TEST(setns) +{ + char path[PATH_MAX]; + + // Test setns with current process's namespace (should succeed) + for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) { + snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]); + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + + // setns to own namespace should succeed + if (strcmp(ns_files[i], "user") != 0) { + TEST_SUCC(setns(nsfd, 0)); + } else { + TEST_ERRNO(setns(nsfd, 0), EINVAL); + } + + TEST_SUCC(close(nsfd)); + } + + // Test setns with invalid fd + TEST_ERRNO(setns(-1, 0), EBADF); + + // Test setns across fork + pid_t pid = fork(); + TEST_RES(pid >= 0, 1); + + if (pid == 0) { + // Child: try to join parent's UTS namespace + snprintf(path, sizeof(path), "/proc/%d/ns/uts", getppid()); + int parent_ns = CHECK(open(path, O_RDONLY)); + + CHECK(setns(parent_ns, 0)); + close(parent_ns); + + exit(0); + } + + int status; + TEST_RES(waitpid(pid, &status, 0), pid); + TEST_RES(WIFEXITED(status), 1); +} +END_TEST() + +FN_TEST(proc_fd_name) +{ + char path[PATH_MAX]; + char buf1[256]; + char buf2[256]; + + for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) { + snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]); + + memset(buf1, 0, sizeof(buf1)); + TEST_SUCC(readlink(path, buf1, sizeof(buf1))); + + int nsfd = TEST_SUCC(open(path, O_RDONLY)); + memset(path, 0, sizeof(path)); + sprintf(path, "/proc/self/fd/%d", nsfd); + + memset(buf2, 0, sizeof(buf2)); + TEST_RES(readlink(path, buf2, sizeof(buf2)), + strcmp(buf1, buf2) == 0); + } +} +END_TEST() \ 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 773de695b..c9a766c60 100755 --- a/test/initramfs/src/apps/scripts/process.sh +++ b/test/initramfs/src/apps/scripts/process.sh @@ -45,6 +45,7 @@ mmap/mmap_shared_filebacked mmap/mmap_readahead mmap/mmap_vmrss namespace/mnt_ns +namespace/proc_nsfs namespace/setns namespace/unshare process/group_session