[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAOQ4uxhwJBLCzfKs0dVFOpcgP=LQU5hkRxVeLLR6g-qOxb9ocQ@mail.gmail.com>
Date: Wed, 10 Sep 2025 19:30:03 +0200
From: Amir Goldstein <amir73il@...il.com>
To: Christian Brauner <brauner@...nel.org>
Cc: Jan Kara <jack@...e.cz>, linux-fsdevel@...r.kernel.org,
Josef Bacik <josef@...icpanda.com>, Jeff Layton <jlayton@...nel.org>, Mike Yuan <me@...dnzj.com>,
Zbigniew Jędrzejewski-Szmek <zbyszek@...waw.pl>,
Lennart Poettering <mzxreary@...inter.de>, Daan De Meyer <daan.j.demeyer@...il.com>,
Aleksa Sarai <cyphar@...har.com>, Alexander Viro <viro@...iv.linux.org.uk>,
Jens Axboe <axboe@...nel.dk>, Tejun Heo <tj@...nel.org>, Johannes Weiner <hannes@...xchg.org>,
Michal Koutný <mkoutny@...e.com>,
Eric Dumazet <edumazet@...gle.com>, Jakub Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>,
Simon Horman <horms@...nel.org>, Chuck Lever <chuck.lever@...cle.com>, linux-nfs@...r.kernel.org,
linux-kselftest@...r.kernel.org, linux-block@...r.kernel.org,
linux-kernel@...r.kernel.org, cgroups@...r.kernel.org, netdev@...r.kernel.org
Subject: Re: [PATCH 32/32] selftests/namespaces: add file handle selftests
On Wed, Sep 10, 2025 at 4:40 PM Christian Brauner <brauner@...nel.org> wrote:
>
> Add a bunch of selftests for namespace file handles.
>
> Signed-off-by: Christian Brauner <brauner@...nel.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@...il.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
>
Powered by blists - more mailing lists