| // SPDX-License-Identifier: GPL-2.0 | |
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <grp.h> | |
| #include <limits.h> | |
| #include <sched.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mount.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include <linux/unistd.h> | |
| #include "../kselftest_harness.h" | |
| #ifndef FD_NSFS_ROOT | |
| #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ | |
| #endif | |
| TEST(nsfs_net_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open a namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/net", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT as unprivileged user */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| if (fd < 0 && errno == EPERM) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "Permission denied for unprivileged user (expected)"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_uts_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open UTS namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/uts", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_ipc_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open IPC namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/ipc", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_pid_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open PID namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/pid", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_mnt_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open mount namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/mnt", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open user namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/user", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_cgroup_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open cgroup namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/cgroup", O_RDONLY); | |
| if (ns_fd < 0) { | |
| SKIP(free(handle); return, "cgroup namespace not available"); | |
| } | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_time_handle) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| struct stat st1, st2; | |
| /* Drop to unprivileged uid/gid */ | |
| ASSERT_EQ(setresgid(65534, 65534, 65534), 0); /* nogroup */ | |
| ASSERT_EQ(setresuid(65534, 65534, 65534), 0); /* nobody */ | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open time namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/time", O_RDONLY); | |
| if (ns_fd < 0) { | |
| SKIP(free(handle); return, "time namespace not available"); | |
| } | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Try to open using FD_NSFS_ROOT */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, | |
| "open_by_handle_at with FD_NSFS_ROOT not supported"); | |
| } | |
| ASSERT_GE(fd, 0); | |
| /* Verify we opened the correct namespace */ | |
| ASSERT_EQ(fstat(ns_fd, &st1), 0); | |
| ASSERT_EQ(fstat(fd, &st2), 0); | |
| ASSERT_EQ(st1.st_ino, st2.st_ino); | |
| ASSERT_EQ(st1.st_dev, st2.st_dev); | |
| close(fd); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_net_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current network namespace */ | |
| ns_fd = open("/proc/self/ns/net", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new network namespace */ | |
| ret = unshare(CLONE_NEWNET); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create network namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Try to open parent's network namespace handle from new user+net namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new network namespace"); | |
| } | |
| /* Should fail with permission denied since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_uts_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current UTS namespace */ | |
| ns_fd = open("/proc/self/ns/uts", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new UTS namespace */ | |
| ret = unshare(CLONE_NEWUTS); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create UTS namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Try to open parent's UTS namespace handle from new user+uts namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new UTS namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_ipc_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current IPC namespace */ | |
| ns_fd = open("/proc/self/ns/ipc", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new IPC namespace */ | |
| ret = unshare(CLONE_NEWIPC); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create IPC namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Try to open parent's IPC namespace handle from new user+ipc namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new IPC namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_mnt_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current mount namespace */ | |
| ns_fd = open("/proc/self/ns/mnt", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new mount namespace */ | |
| ret = unshare(CLONE_NEWNS); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create mount namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Try to open parent's mount namespace handle from new user+mnt namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new mount namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_cgroup_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current cgroup namespace */ | |
| ns_fd = open("/proc/self/ns/cgroup", O_RDONLY); | |
| if (ns_fd < 0) { | |
| SKIP(free(handle); close(pipefd[0]); close(pipefd[1]); | |
| return, "cgroup namespace not available"); | |
| } | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new cgroup namespace */ | |
| ret = unshare(CLONE_NEWCGROUP); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create cgroup namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Try to open parent's cgroup namespace handle from new user+cgroup namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new cgroup namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_pid_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current PID namespace */ | |
| ns_fd = open("/proc/self/ns/pid", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new PID namespace - requires fork to take effect */ | |
| ret = unshare(CLONE_NEWPID); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create PID namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Fork again for PID namespace to take effect */ | |
| pid_t child_pid = fork(); | |
| if (child_pid < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to fork in PID namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| if (child_pid == 0) { | |
| /* Grandchild in new PID namespace */ | |
| /* Try to open parent's PID namespace handle from new user+pid namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", | |
| 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Wait for grandchild */ | |
| waitpid(child_pid, NULL, 0); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new PID namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_user_time_namespace_isolation) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| pid_t pid; | |
| int status; | |
| int pipefd[2]; | |
| char result; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Create pipe for communication */ | |
| ASSERT_EQ(pipe(pipefd), 0); | |
| /* Get handle for current time namespace */ | |
| ns_fd = open("/proc/self/ns/time", O_RDONLY); | |
| if (ns_fd < 0) { | |
| SKIP(free(handle); close(pipefd[0]); close(pipefd[1]); | |
| return, "time namespace not available"); | |
| } | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); close(pipefd[0]); | |
| close(pipefd[1]); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| close(ns_fd); | |
| pid = fork(); | |
| ASSERT_GE(pid, 0); | |
| if (pid == 0) { | |
| /* Child process */ | |
| close(pipefd[0]); | |
| /* First create new user namespace to drop privileges */ | |
| ret = unshare(CLONE_NEWUSER); | |
| if (ret < 0) { | |
| write(pipefd[1], "U", | |
| 1); /* Unable to create user namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Write uid/gid mappings to maintain some capabilities */ | |
| int uid_map_fd = open("/proc/self/uid_map", O_WRONLY); | |
| int gid_map_fd = open("/proc/self/gid_map", O_WRONLY); | |
| int setgroups_fd = open("/proc/self/setgroups", O_WRONLY); | |
| if (uid_map_fd < 0 || gid_map_fd < 0 || setgroups_fd < 0) { | |
| write(pipefd[1], "M", 1); /* Unable to set mappings */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Disable setgroups to allow gid mapping */ | |
| write(setgroups_fd, "deny", 4); | |
| close(setgroups_fd); | |
| /* Map current uid/gid to root in the new namespace */ | |
| char mapping[64]; | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getuid()); | |
| write(uid_map_fd, mapping, strlen(mapping)); | |
| close(uid_map_fd); | |
| snprintf(mapping, sizeof(mapping), "0 %d 1", getgid()); | |
| write(gid_map_fd, mapping, strlen(mapping)); | |
| close(gid_map_fd); | |
| /* Now create new time namespace - requires fork to take effect */ | |
| ret = unshare(CLONE_NEWTIME); | |
| if (ret < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to create time namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Fork again for time namespace to take effect */ | |
| pid_t child_pid = fork(); | |
| if (child_pid < 0) { | |
| write(pipefd[1], "N", | |
| 1); /* Unable to fork in time namespace */ | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| if (child_pid == 0) { | |
| /* Grandchild in new time namespace */ | |
| /* Try to open parent's time namespace handle from new user+time namespace */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY); | |
| if (fd >= 0) { | |
| /* Should NOT succeed - we're in a different user namespace */ | |
| write(pipefd[1], "S", | |
| 1); /* Unexpected success */ | |
| close(fd); | |
| } else if (errno == ESTALE) { | |
| /* Expected: Stale file handle */ | |
| write(pipefd[1], "P", 1); | |
| } else { | |
| /* Other error */ | |
| write(pipefd[1], "F", 1); | |
| } | |
| close(pipefd[1]); | |
| exit(0); | |
| } | |
| /* Wait for grandchild */ | |
| waitpid(child_pid, NULL, 0); | |
| exit(0); | |
| } | |
| /* Parent process */ | |
| close(pipefd[1]); | |
| ASSERT_EQ(read(pipefd[0], &result, 1), 1); | |
| waitpid(pid, &status, 0); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| if (result == 'U') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new user namespace"); | |
| } | |
| if (result == 'M') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot set uid/gid mappings"); | |
| } | |
| if (result == 'N') { | |
| SKIP(free(handle); close(pipefd[0]); | |
| return, "Cannot create new time namespace"); | |
| } | |
| /* Should fail with ESTALE since we're in a different user namespace */ | |
| ASSERT_EQ(result, 'P'); | |
| close(pipefd[0]); | |
| free(handle); | |
| } | |
| TEST(nsfs_open_flags) | |
| { | |
| struct file_handle *handle; | |
| int mount_id; | |
| int ret; | |
| int fd; | |
| int ns_fd; | |
| handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); | |
| ASSERT_NE(handle, NULL); | |
| /* Open a namespace file descriptor */ | |
| ns_fd = open("/proc/self/ns/net", O_RDONLY); | |
| ASSERT_GE(ns_fd, 0); | |
| /* Get handle for the namespace */ | |
| handle->handle_bytes = MAX_HANDLE_SZ; | |
| ret = name_to_handle_at(ns_fd, "", handle, &mount_id, AT_EMPTY_PATH); | |
| if (ret < 0 && errno == EOPNOTSUPP) { | |
| SKIP(free(handle); close(ns_fd); | |
| return, "nsfs doesn't support file handles"); | |
| } | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_GT(handle->handle_bytes, 0); | |
| /* Test invalid flags that should fail */ | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_WRONLY); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, EPERM); | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDWR); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, EPERM); | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_TRUNC); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, EPERM); | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_DIRECT); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, EINVAL); | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_TMPFILE); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, EINVAL); | |
| fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_DIRECTORY); | |
| ASSERT_LT(fd, 0); | |
| ASSERT_EQ(errno, ENOTDIR); | |
| close(ns_fd); | |
| free(handle); | |
| } | |
| TEST_HARNESS_MAIN |