Add a bunch of selftests for namespace file handles.
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
tools/testing/selftests/namespaces/.gitignore | 1 +
tools/testing/selftests/namespaces/Makefile | 2 +-
.../selftests/namespaces/file_handle_test.c | 1410 ++++++++++++++++++++
3 files changed, 1412 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/namespaces/.gitignore b/tools/testing/selftests/namespaces/.gitignore
index c1e8d634dd21..7639dbf58bbf 100644
--- a/tools/testing/selftests/namespaces/.gitignore
+++ b/tools/testing/selftests/namespaces/.gitignore
@@ -1 +1,2 @@
nsid_test
+file_handle_test
diff --git a/tools/testing/selftests/namespaces/Makefile b/tools/testing/selftests/namespaces/Makefile
index 9280c703533e..f6c117ce2c2b 100644
--- a/tools/testing/selftests/namespaces/Makefile
+++ b/tools/testing/selftests/namespaces/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
-TEST_GEN_PROGS := nsid_test
+TEST_GEN_PROGS := nsid_test file_handle_test
include ../lib.mk
diff --git a/tools/testing/selftests/namespaces/file_handle_test.c b/tools/testing/selftests/namespaces/file_handle_test.c
new file mode 100644
index 000000000000..87573fa06990
--- /dev/null
+++ b/tools/testing/selftests/namespaces/file_handle_test.c
@@ -0,0 +1,1410 @@
+// 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open a namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/net");
+ ns_fd = open(ns_path, 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_uts_handle)
+{
+ struct file_handle *handle;
+ int mount_id;
+ int ret;
+ int fd;
+ int ns_fd;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open UTS namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/uts");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open IPC namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/ipc");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open PID namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/pid");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open mount namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/mnt");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open user namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/user");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open cgroup namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/cgroup");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+ struct stat st1, st2;
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open time namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/time");
+ ns_fd = open(ns_path, 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;
+ char ns_path[256];
+
+ handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
+ ASSERT_NE(handle, NULL);
+
+ /* Open a namespace file descriptor */
+ snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/net");
+ ns_fd = open(ns_path, 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_CREAT);
+ ASSERT_LT(fd, 0);
+ ASSERT_EQ(errno, EINVAL);
+
+ fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_TRUNC);
+ ASSERT_LT(fd, 0);
+ ASSERT_EQ(errno, EINVAL);
+
+ fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_APPEND);
+ ASSERT_LT(fd, 0);
+ ASSERT_EQ(errno, EINVAL);
+
+ fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_DIRECT);
+ ASSERT_LT(fd, 0);
+ ASSERT_EQ(errno, EINVAL);
+
+ close(ns_fd);
+ free(handle);
+}
+
+TEST_HARNESS_MAIN
--
2.47.3
On 9/10/25 7:37 AM, Christian Brauner wrote: > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/net"); > + ns_fd = open(ns_path, O_RDONLY); Here and also in TEST(nsfs_uts_handle), ns_path is not modified. Does this mean that "/proc/self/ns/net" can be stored in a static const char array and also that the snprintf() call can be left out? In case I would have missed the reason why the path is copied, how about using asprintf() or strdup() instead of snprintf()? Thanks, Bart.
On Wed, Sep 10, 2025 at 4:40 PM Christian Brauner <brauner@kernel.org> wrote: > > Add a bunch of selftests for namespace file handles. > > Signed-off-by: Christian Brauner <brauner@kernel.org> Obviously, I did not go over every single line, but for the general test template and test coverage you may add: Reviewed-by: Amir Goldstein <amir73il@gmail.com> However, see my comment on file handle support patch. The test matrix is incomplete. Maybe it would be complete if test is run as root and then as non root, but then I think the test needs some changes for running as root and opening non-self ns. I am not sure what the standard is wrt running the selftests as root /non-root. I see that the userns isolation tests do: /* Map current uid/gid to root in the new namespace */ Are you assuming that non root is running this test or am I missing something? Wouldn't mapping uid 0 to uid 0 in the new userns cause the test to fail because opening by handle will succeed? Thanks, Amir. > --- > tools/testing/selftests/namespaces/.gitignore | 1 + > tools/testing/selftests/namespaces/Makefile | 2 +- > .../selftests/namespaces/file_handle_test.c | 1410 ++++++++++++++++++++ > 3 files changed, 1412 insertions(+), 1 deletion(-) > > diff --git a/tools/testing/selftests/namespaces/.gitignore b/tools/testing/selftests/namespaces/.gitignore > index c1e8d634dd21..7639dbf58bbf 100644 > --- a/tools/testing/selftests/namespaces/.gitignore > +++ b/tools/testing/selftests/namespaces/.gitignore > @@ -1 +1,2 @@ > nsid_test > +file_handle_test > diff --git a/tools/testing/selftests/namespaces/Makefile b/tools/testing/selftests/namespaces/Makefile > index 9280c703533e..f6c117ce2c2b 100644 > --- a/tools/testing/selftests/namespaces/Makefile > +++ b/tools/testing/selftests/namespaces/Makefile > @@ -1,7 +1,7 @@ > # SPDX-License-Identifier: GPL-2.0-only > CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) > > -TEST_GEN_PROGS := nsid_test > +TEST_GEN_PROGS := nsid_test file_handle_test > > include ../lib.mk > > diff --git a/tools/testing/selftests/namespaces/file_handle_test.c b/tools/testing/selftests/namespaces/file_handle_test.c > new file mode 100644 > index 000000000000..87573fa06990 > --- /dev/null > +++ b/tools/testing/selftests/namespaces/file_handle_test.c > @@ -0,0 +1,1410 @@ > +// 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open a namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/net"); > + ns_fd = open(ns_path, 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_uts_handle) > +{ > + struct file_handle *handle; > + int mount_id; > + int ret; > + int fd; > + int ns_fd; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open UTS namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/uts"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open IPC namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/ipc"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open PID namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/pid"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open mount namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/mnt"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open user namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/user"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open cgroup namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/cgroup"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + struct stat st1, st2; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open time namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/time"); > + ns_fd = open(ns_path, 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; > + char ns_path[256]; > + > + handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ); > + ASSERT_NE(handle, NULL); > + > + /* Open a namespace file descriptor */ > + snprintf(ns_path, sizeof(ns_path), "/proc/self/ns/net"); > + ns_fd = open(ns_path, 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_CREAT); > + ASSERT_LT(fd, 0); > + ASSERT_EQ(errno, EINVAL); > + > + fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_TRUNC); > + ASSERT_LT(fd, 0); > + ASSERT_EQ(errno, EINVAL); > + > + fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_APPEND); > + ASSERT_LT(fd, 0); > + ASSERT_EQ(errno, EINVAL); > + > + fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_DIRECT); > + ASSERT_LT(fd, 0); > + ASSERT_EQ(errno, EINVAL); > + > + close(ns_fd); > + free(handle); > +} > + > +TEST_HARNESS_MAIN > > -- > 2.47.3 >
© 2016 - 2025 Red Hat, Inc.