mirror of https://github.com/torvalds/linux.git
2117 lines
54 KiB
C
2117 lines
54 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <linux/nsfs.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include "../kselftest_harness.h"
|
|
#include "../filesystems/utils.h"
|
|
|
|
#ifndef FD_NSFS_ROOT
|
|
#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
|
|
#endif
|
|
|
|
#ifndef FILEID_NSFS
|
|
#define FILEID_NSFS 0xf1
|
|
#endif
|
|
|
|
/*
|
|
* Test that initial namespaces can be reopened via file handle.
|
|
* Initial namespaces should have active ref count of 1 from boot.
|
|
*/
|
|
TEST(init_ns_always_active)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd1, fd2;
|
|
struct stat st1, st2;
|
|
|
|
handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
|
|
ASSERT_NE(handle, NULL);
|
|
|
|
/* Open initial network namespace */
|
|
fd1 = open("/proc/1/ns/net", O_RDONLY);
|
|
ASSERT_GE(fd1, 0);
|
|
|
|
/* Get file handle for initial namespace */
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd1, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
if (ret < 0 && errno == EOPNOTSUPP) {
|
|
SKIP(free(handle); close(fd1);
|
|
return, "nsfs doesn't support file handles");
|
|
}
|
|
ASSERT_EQ(ret, 0);
|
|
|
|
/* Close the namespace fd */
|
|
close(fd1);
|
|
|
|
/* Try to reopen via file handle - should succeed since init ns is always active */
|
|
fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
if (fd2 < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) {
|
|
SKIP(free(handle);
|
|
return, "open_by_handle_at with FD_NSFS_ROOT not supported");
|
|
}
|
|
ASSERT_GE(fd2, 0);
|
|
|
|
/* Verify we opened the same namespace */
|
|
fd1 = open("/proc/1/ns/net", O_RDONLY);
|
|
ASSERT_GE(fd1, 0);
|
|
ASSERT_EQ(fstat(fd1, &st1), 0);
|
|
ASSERT_EQ(fstat(fd2, &st2), 0);
|
|
ASSERT_EQ(st1.st_ino, st2.st_ino);
|
|
|
|
close(fd1);
|
|
close(fd2);
|
|
free(handle);
|
|
}
|
|
|
|
/*
|
|
* Test namespace lifecycle: create a namespace in a child process,
|
|
* get a file handle while it's active, then try to reopen after
|
|
* the process exits (namespace becomes inactive).
|
|
*/
|
|
TEST(ns_inactive_after_exit)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd;
|
|
int pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
|
|
/* Create pipe for passing file handle from child */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new network namespace */
|
|
ret = unshare(CLONE_NEWNET);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Open our new namespace */
|
|
fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Get file handle for the namespace */
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(fd);
|
|
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Send handle to parent */
|
|
write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
close(pipefd[1]);
|
|
|
|
/* Exit - namespace should become inactive */
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
/* Read file handle from child */
|
|
ret = read(pipefd[0], buf, sizeof(buf));
|
|
close(pipefd[0]);
|
|
|
|
/* Wait for child to exit */
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
ASSERT_GT(ret, 0);
|
|
handle = (struct file_handle *)buf;
|
|
|
|
/* Try to reopen namespace - should fail with ENOENT since it's inactive */
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_LT(fd, 0);
|
|
/* Should fail with ENOENT (namespace inactive) or ESTALE */
|
|
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
|
|
}
|
|
|
|
/*
|
|
* Test that a namespace remains active while a process is using it,
|
|
* even after the creating process exits.
|
|
*/
|
|
TEST(ns_active_with_multiple_processes)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd;
|
|
int pipefd[2];
|
|
int syncpipe[2];
|
|
pid_t pid1, pid2;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
char sync_byte;
|
|
|
|
/* Create pipes for communication */
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
ASSERT_EQ(pipe(syncpipe), 0);
|
|
|
|
pid1 = fork();
|
|
ASSERT_GE(pid1, 0);
|
|
|
|
if (pid1 == 0) {
|
|
/* First child - creates namespace */
|
|
close(pipefd[0]);
|
|
close(syncpipe[1]);
|
|
|
|
/* Create new network namespace */
|
|
ret = unshare(CLONE_NEWNET);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Open and get handle */
|
|
fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (fd < 0) {
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(fd);
|
|
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Send handle to parent */
|
|
write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
close(pipefd[1]);
|
|
|
|
/* Wait for signal before exiting */
|
|
read(syncpipe[0], &sync_byte, 1);
|
|
close(syncpipe[0]);
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent reads handle */
|
|
close(pipefd[1]);
|
|
ret = read(pipefd[0], buf, sizeof(buf));
|
|
close(pipefd[0]);
|
|
ASSERT_GT(ret, 0);
|
|
|
|
handle = (struct file_handle *)buf;
|
|
|
|
/* Create second child that will keep namespace active */
|
|
pid2 = fork();
|
|
ASSERT_GE(pid2, 0);
|
|
|
|
if (pid2 == 0) {
|
|
/* Second child - reopens the namespace */
|
|
close(syncpipe[0]);
|
|
close(syncpipe[1]);
|
|
|
|
/* Open the namespace via handle */
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
if (fd < 0) {
|
|
exit(1);
|
|
}
|
|
|
|
/* Join the namespace */
|
|
ret = setns(fd, CLONE_NEWNET);
|
|
close(fd);
|
|
if (ret < 0) {
|
|
exit(1);
|
|
}
|
|
|
|
/* Sleep to keep namespace active */
|
|
sleep(1);
|
|
exit(0);
|
|
}
|
|
|
|
/* Let second child enter the namespace */
|
|
usleep(100000); /* 100ms */
|
|
|
|
/* Signal first child to exit */
|
|
close(syncpipe[0]);
|
|
sync_byte = 'X';
|
|
write(syncpipe[1], &sync_byte, 1);
|
|
close(syncpipe[1]);
|
|
|
|
/* Wait for first child */
|
|
waitpid(pid1, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
|
|
/* Namespace should still be active because second child is using it */
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_GE(fd, 0);
|
|
close(fd);
|
|
|
|
/* Wait for second child */
|
|
waitpid(pid2, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
}
|
|
|
|
/*
|
|
* Test user namespace active ref tracking via credential lifecycle
|
|
*/
|
|
TEST(userns_active_ref_lifecycle)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd;
|
|
int pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new user namespace */
|
|
ret = unshare(CLONE_NEWUSER);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Set up uid/gid mappings */
|
|
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(setgroups_fd, "deny", 4);
|
|
close(setgroups_fd);
|
|
|
|
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);
|
|
}
|
|
|
|
/* Get file handle */
|
|
fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(fd);
|
|
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Send handle to parent */
|
|
write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent */
|
|
close(pipefd[1]);
|
|
ret = read(pipefd[0], buf, sizeof(buf));
|
|
close(pipefd[0]);
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
ASSERT_GT(ret, 0);
|
|
handle = (struct file_handle *)buf;
|
|
|
|
/* Namespace should be inactive after all tasks exit */
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_LT(fd, 0);
|
|
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
|
|
}
|
|
|
|
/*
|
|
* Test PID namespace active ref tracking
|
|
*/
|
|
TEST(pidns_active_ref_lifecycle)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd;
|
|
int pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Create new PID namespace */
|
|
ret = unshare(CLONE_NEWPID);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
/* Fork to actually enter the PID namespace */
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
if (child == 0) {
|
|
/* Grandchild - in new PID namespace */
|
|
fd = open("/proc/self/ns/pid", O_RDONLY);
|
|
if (fd < 0) {
|
|
exit(1);
|
|
}
|
|
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(fd);
|
|
|
|
if (ret < 0) {
|
|
exit(1);
|
|
}
|
|
|
|
/* Send handle to grandparent */
|
|
write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
/* Wait for grandchild */
|
|
waitpid(child, NULL, 0);
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent */
|
|
close(pipefd[1]);
|
|
ret = read(pipefd[0], buf, sizeof(buf));
|
|
close(pipefd[0]);
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
ASSERT_GT(ret, 0);
|
|
handle = (struct file_handle *)buf;
|
|
|
|
/* Namespace should be inactive after all processes exit */
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_LT(fd, 0);
|
|
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
|
|
}
|
|
|
|
/*
|
|
* Test that an open file descriptor keeps a namespace active.
|
|
* Even after the creating process exits, the namespace should remain
|
|
* active as long as an fd is held open.
|
|
*/
|
|
TEST(ns_fd_keeps_active)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int nsfd;
|
|
int pipe_child_ready[2];
|
|
int pipe_parent_ready[2];
|
|
pid_t pid;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
char sync_byte;
|
|
char proc_path[64];
|
|
|
|
ASSERT_EQ(pipe(pipe_child_ready), 0);
|
|
ASSERT_EQ(pipe(pipe_parent_ready), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipe_child_ready[0]);
|
|
close(pipe_parent_ready[1]);
|
|
|
|
TH_LOG("Child: creating new network namespace");
|
|
|
|
/* Create new network namespace */
|
|
ret = unshare(CLONE_NEWNET);
|
|
if (ret < 0) {
|
|
TH_LOG("Child: unshare(CLONE_NEWNET) failed: %s", strerror(errno));
|
|
close(pipe_child_ready[1]);
|
|
close(pipe_parent_ready[0]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: network namespace created successfully");
|
|
|
|
/* Get file handle for the namespace */
|
|
nsfd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (nsfd < 0) {
|
|
TH_LOG("Child: failed to open /proc/self/ns/net: %s", strerror(errno));
|
|
close(pipe_child_ready[1]);
|
|
close(pipe_parent_ready[0]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: opened namespace fd %d", nsfd);
|
|
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(nsfd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(nsfd);
|
|
|
|
if (ret < 0) {
|
|
TH_LOG("Child: name_to_handle_at failed: %s", strerror(errno));
|
|
close(pipe_child_ready[1]);
|
|
close(pipe_parent_ready[0]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: got file handle (bytes=%u)", handle->handle_bytes);
|
|
|
|
/* Send file handle to parent */
|
|
ret = write(pipe_child_ready[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
TH_LOG("Child: sent %d bytes of file handle to parent", ret);
|
|
close(pipe_child_ready[1]);
|
|
|
|
/* Wait for parent to open the fd */
|
|
TH_LOG("Child: waiting for parent to open fd");
|
|
ret = read(pipe_parent_ready[0], &sync_byte, 1);
|
|
close(pipe_parent_ready[0]);
|
|
|
|
TH_LOG("Child: parent signaled (read %d bytes), exiting now", ret);
|
|
/* Exit - namespace should stay active because parent holds fd */
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent process */
|
|
close(pipe_child_ready[1]);
|
|
close(pipe_parent_ready[0]);
|
|
|
|
TH_LOG("Parent: reading file handle from child");
|
|
|
|
/* Read file handle from child */
|
|
ret = read(pipe_child_ready[0], buf, sizeof(buf));
|
|
close(pipe_child_ready[0]);
|
|
ASSERT_GT(ret, 0);
|
|
handle = (struct file_handle *)buf;
|
|
|
|
TH_LOG("Parent: received %d bytes, handle size=%u", ret, handle->handle_bytes);
|
|
|
|
/* Open the child's namespace while it's still alive */
|
|
snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid);
|
|
TH_LOG("Parent: opening child's namespace at %s", proc_path);
|
|
nsfd = open(proc_path, O_RDONLY);
|
|
if (nsfd < 0) {
|
|
TH_LOG("Parent: failed to open %s: %s", proc_path, strerror(errno));
|
|
close(pipe_parent_ready[1]);
|
|
kill(pid, SIGKILL);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open child's namespace");
|
|
}
|
|
|
|
TH_LOG("Parent: opened child's namespace, got fd %d", nsfd);
|
|
|
|
/* Signal child that we have the fd */
|
|
sync_byte = 'G';
|
|
write(pipe_parent_ready[1], &sync_byte, 1);
|
|
close(pipe_parent_ready[1]);
|
|
TH_LOG("Parent: signaled child that we have the fd");
|
|
|
|
/* Wait for child to exit */
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
TH_LOG("Child exited, parent holds fd %d to namespace", nsfd);
|
|
|
|
/*
|
|
* Namespace should still be ACTIVE because we hold an fd.
|
|
* We should be able to reopen it via file handle.
|
|
*/
|
|
TH_LOG("Attempting to reopen namespace via file handle (should succeed - fd held)");
|
|
int fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_GE(fd2, 0);
|
|
|
|
TH_LOG("Successfully reopened namespace via file handle, got fd %d", fd2);
|
|
|
|
/* Verify it's the same namespace */
|
|
struct stat st1, st2;
|
|
ASSERT_EQ(fstat(nsfd, &st1), 0);
|
|
ASSERT_EQ(fstat(fd2, &st2), 0);
|
|
TH_LOG("Namespace inodes: nsfd=%lu, fd2=%lu", st1.st_ino, st2.st_ino);
|
|
ASSERT_EQ(st1.st_ino, st2.st_ino);
|
|
close(fd2);
|
|
|
|
/* Now close the fd - namespace should become inactive */
|
|
TH_LOG("Closing fd %d - namespace should become inactive", nsfd);
|
|
close(nsfd);
|
|
|
|
/* Now reopening should fail - namespace is inactive */
|
|
TH_LOG("Attempting to reopen namespace via file handle (should fail - inactive)");
|
|
fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_LT(fd2, 0);
|
|
/* Should fail with ENOENT (inactive) or ESTALE (gone) */
|
|
TH_LOG("Reopen failed as expected: %s (errno=%d)", strerror(errno), errno);
|
|
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
|
|
}
|
|
|
|
/*
|
|
* Test hierarchical active reference propagation.
|
|
* When a child namespace is active, its owning user namespace should also
|
|
* be active automatically due to hierarchical active reference propagation.
|
|
* This ensures parents are always reachable when children are active.
|
|
*/
|
|
TEST(ns_parent_always_reachable)
|
|
{
|
|
struct file_handle *parent_handle, *child_handle;
|
|
int ret;
|
|
int child_nsfd;
|
|
int pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 parent_id, child_id;
|
|
char parent_buf[sizeof(*parent_handle) + MAX_HANDLE_SZ];
|
|
char child_buf[sizeof(*child_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
TH_LOG("Child: creating parent user namespace and setting up mappings");
|
|
|
|
/* Create parent user namespace with mappings */
|
|
ret = setup_userns();
|
|
if (ret < 0) {
|
|
TH_LOG("Child: setup_userns() for parent failed: %s", strerror(errno));
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: parent user namespace created, now uid=%d gid=%d", getuid(), getgid());
|
|
|
|
/* Get namespace ID for parent user namespace */
|
|
int parent_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (parent_fd < 0) {
|
|
TH_LOG("Child: failed to open parent /proc/self/ns/user: %s", strerror(errno));
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: opened parent userns fd %d", parent_fd);
|
|
|
|
if (ioctl(parent_fd, NS_GET_ID, &parent_id) < 0) {
|
|
TH_LOG("Child: NS_GET_ID for parent failed: %s", strerror(errno));
|
|
close(parent_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(parent_fd);
|
|
|
|
TH_LOG("Child: got parent namespace ID %llu", (unsigned long long)parent_id);
|
|
|
|
/* Create child user namespace within parent */
|
|
TH_LOG("Child: creating nested child user namespace");
|
|
ret = setup_userns();
|
|
if (ret < 0) {
|
|
TH_LOG("Child: setup_userns() for child failed: %s", strerror(errno));
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: nested child user namespace created, uid=%d gid=%d", getuid(), getgid());
|
|
|
|
/* Get namespace ID for child user namespace */
|
|
int child_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (child_fd < 0) {
|
|
TH_LOG("Child: failed to open child /proc/self/ns/user: %s", strerror(errno));
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
TH_LOG("Child: opened child userns fd %d", child_fd);
|
|
|
|
if (ioctl(child_fd, NS_GET_ID, &child_id) < 0) {
|
|
TH_LOG("Child: NS_GET_ID for child failed: %s", strerror(errno));
|
|
close(child_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(child_fd);
|
|
|
|
TH_LOG("Child: got child namespace ID %llu", (unsigned long long)child_id);
|
|
|
|
/* Send both namespace IDs to parent */
|
|
TH_LOG("Child: sending both namespace IDs to parent");
|
|
write(pipefd[1], &parent_id, sizeof(parent_id));
|
|
write(pipefd[1], &child_id, sizeof(child_id));
|
|
close(pipefd[1]);
|
|
|
|
TH_LOG("Child: exiting - parent userns should become inactive");
|
|
/* Exit - parent user namespace should become inactive */
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent process */
|
|
close(pipefd[1]);
|
|
|
|
TH_LOG("Parent: reading both namespace IDs from child");
|
|
|
|
/* Read both namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &parent_id, sizeof(parent_id));
|
|
if (ret != sizeof(parent_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read parent namespace ID from child");
|
|
}
|
|
|
|
ret = read(pipefd[0], &child_id, sizeof(child_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(child_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read child namespace ID from child");
|
|
}
|
|
|
|
TH_LOG("Parent: received parent_id=%llu, child_id=%llu",
|
|
(unsigned long long)parent_id, (unsigned long long)child_id);
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
parent_handle = (struct file_handle *)parent_buf;
|
|
parent_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
parent_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *parent_fh = (struct nsfs_file_handle *)parent_handle->f_handle;
|
|
parent_fh->ns_id = parent_id;
|
|
parent_fh->ns_type = 0;
|
|
parent_fh->ns_inum = 0;
|
|
|
|
child_handle = (struct file_handle *)child_buf;
|
|
child_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
child_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *child_fh = (struct nsfs_file_handle *)child_handle->f_handle;
|
|
child_fh->ns_id = child_id;
|
|
child_fh->ns_type = 0;
|
|
child_fh->ns_inum = 0;
|
|
|
|
TH_LOG("Parent: opening child namespace BEFORE child exits");
|
|
|
|
/* Open child namespace while child is still alive to keep it active */
|
|
child_nsfd = open_by_handle_at(FD_NSFS_ROOT, child_handle, O_RDONLY);
|
|
if (child_nsfd < 0) {
|
|
TH_LOG("Failed to open child namespace: %s (errno=%d)", strerror(errno), errno);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open child namespace");
|
|
}
|
|
|
|
TH_LOG("Opened child namespace fd %d", child_nsfd);
|
|
|
|
/* Now wait for child to exit */
|
|
TH_LOG("Parent: waiting for child to exit");
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
TH_LOG("Child process exited, parent holds fd to child namespace");
|
|
|
|
/*
|
|
* With hierarchical active reference propagation:
|
|
* Since the child namespace is active (parent process holds fd),
|
|
* the parent user namespace should ALSO be active automatically.
|
|
* This is because when we took an active reference on the child,
|
|
* it propagated up to the owning user namespace.
|
|
*/
|
|
TH_LOG("Attempting to reopen parent namespace (should SUCCEED - hierarchical propagation)");
|
|
int parent_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_GE(parent_fd, 0);
|
|
|
|
TH_LOG("SUCCESS: Parent namespace is active (fd=%d) due to active child", parent_fd);
|
|
|
|
/* Verify we can also get parent via NS_GET_USERNS */
|
|
TH_LOG("Verifying NS_GET_USERNS also works");
|
|
int parent_fd2 = ioctl(child_nsfd, NS_GET_USERNS);
|
|
if (parent_fd2 < 0) {
|
|
close(parent_fd);
|
|
close(child_nsfd);
|
|
TH_LOG("NS_GET_USERNS failed: %s (errno=%d)", strerror(errno), errno);
|
|
SKIP(return, "NS_GET_USERNS not supported or failed");
|
|
}
|
|
|
|
TH_LOG("NS_GET_USERNS succeeded, got parent fd %d", parent_fd2);
|
|
|
|
/* Verify both methods give us the same namespace */
|
|
struct stat st1, st2;
|
|
ASSERT_EQ(fstat(parent_fd, &st1), 0);
|
|
ASSERT_EQ(fstat(parent_fd2, &st2), 0);
|
|
TH_LOG("Parent namespace inodes: parent_fd=%lu, parent_fd2=%lu", st1.st_ino, st2.st_ino);
|
|
ASSERT_EQ(st1.st_ino, st2.st_ino);
|
|
|
|
/*
|
|
* Close child fd - parent should remain active because we still
|
|
* hold direct references to it (parent_fd and parent_fd2).
|
|
*/
|
|
TH_LOG("Closing child fd - parent should remain active (direct refs held)");
|
|
close(child_nsfd);
|
|
|
|
/* Parent should still be openable */
|
|
TH_LOG("Verifying parent still active via file handle");
|
|
int parent_fd3 = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_GE(parent_fd3, 0);
|
|
close(parent_fd3);
|
|
|
|
TH_LOG("Closing all fds to parent namespace");
|
|
close(parent_fd);
|
|
close(parent_fd2);
|
|
|
|
/* Both should now be inactive */
|
|
TH_LOG("Attempting to reopen parent (should fail - inactive, no refs)");
|
|
parent_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_LT(parent_fd, 0);
|
|
TH_LOG("Parent inactive as expected: %s (errno=%d)", strerror(errno), errno);
|
|
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
|
|
}
|
|
|
|
/*
|
|
* Test that bind mounts keep namespaces in the tree even when inactive
|
|
*/
|
|
TEST(ns_bind_mount_keeps_in_tree)
|
|
{
|
|
struct file_handle *handle;
|
|
int mount_id;
|
|
int ret;
|
|
int fd;
|
|
int pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
|
|
char tmpfile[] = "/tmp/ns-test-XXXXXX";
|
|
int tmpfd;
|
|
|
|
/* Create temporary file for bind mount */
|
|
tmpfd = mkstemp(tmpfile);
|
|
if (tmpfd < 0) {
|
|
SKIP(return, "Cannot create temporary file");
|
|
}
|
|
close(tmpfd);
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
close(pipefd[0]);
|
|
|
|
/* Unshare mount namespace and make mounts private to avoid propagation */
|
|
ret = unshare(CLONE_NEWNS);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
ret = mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Create new network namespace */
|
|
ret = unshare(CLONE_NEWNET);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Bind mount the namespace */
|
|
ret = mount("/proc/self/ns/net", tmpfile, NULL, MS_BIND, NULL);
|
|
if (ret < 0) {
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Get file handle */
|
|
fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (fd < 0) {
|
|
umount(tmpfile);
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
|
|
handle = (struct file_handle *)buf;
|
|
handle->handle_bytes = MAX_HANDLE_SZ;
|
|
ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
|
|
close(fd);
|
|
|
|
if (ret < 0) {
|
|
umount(tmpfile);
|
|
close(pipefd[1]);
|
|
unlink(tmpfile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Send handle to parent */
|
|
write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
/* Parent */
|
|
close(pipefd[1]);
|
|
ret = read(pipefd[0], buf, sizeof(buf));
|
|
close(pipefd[0]);
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
ASSERT_GT(ret, 0);
|
|
handle = (struct file_handle *)buf;
|
|
|
|
/*
|
|
* Namespace should be inactive but still in tree due to bind mount.
|
|
* Reopening should fail with ENOENT (inactive) not ESTALE (not in tree).
|
|
*/
|
|
fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
|
|
ASSERT_LT(fd, 0);
|
|
/* Should be ENOENT (inactive) since bind mount keeps it in tree */
|
|
if (errno != ENOENT && errno != ESTALE) {
|
|
TH_LOG("Unexpected error: %d", errno);
|
|
}
|
|
|
|
/* Cleanup */
|
|
umount(tmpfile);
|
|
unlink(tmpfile);
|
|
}
|
|
|
|
/*
|
|
* Test multi-level hierarchy (3+ levels deep).
|
|
* Grandparent → Parent → Child
|
|
* When child is active, both parent AND grandparent should be active.
|
|
*/
|
|
TEST(ns_multilevel_hierarchy)
|
|
{
|
|
struct file_handle *gp_handle, *p_handle, *c_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 gp_id, p_id, c_id;
|
|
char gp_buf[sizeof(*gp_handle) + MAX_HANDLE_SZ];
|
|
char p_buf[sizeof(*p_handle) + MAX_HANDLE_SZ];
|
|
char c_buf[sizeof(*c_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
/* Create grandparent user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int gp_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (gp_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(gp_fd, NS_GET_ID, &gp_id) < 0) {
|
|
close(gp_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(gp_fd);
|
|
|
|
/* Create parent user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int p_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (p_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
|
|
close(p_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(p_fd);
|
|
|
|
/* Create child user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int c_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (c_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(c_fd, NS_GET_ID, &c_id) < 0) {
|
|
close(c_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(c_fd);
|
|
|
|
/* Send all three namespace IDs */
|
|
write(pipefd[1], &gp_id, sizeof(gp_id));
|
|
write(pipefd[1], &p_id, sizeof(p_id));
|
|
write(pipefd[1], &c_id, sizeof(c_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &gp_id, sizeof(gp_id));
|
|
if (ret != sizeof(gp_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read grandparent namespace ID from child");
|
|
}
|
|
|
|
ret = read(pipefd[0], &p_id, sizeof(p_id));
|
|
if (ret != sizeof(p_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read parent namespace ID from child");
|
|
}
|
|
|
|
ret = read(pipefd[0], &c_id, sizeof(c_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(c_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read child namespace ID from child");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
gp_handle = (struct file_handle *)gp_buf;
|
|
gp_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
gp_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *gp_fh = (struct nsfs_file_handle *)gp_handle->f_handle;
|
|
gp_fh->ns_id = gp_id;
|
|
gp_fh->ns_type = 0;
|
|
gp_fh->ns_inum = 0;
|
|
|
|
p_handle = (struct file_handle *)p_buf;
|
|
p_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
p_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)p_handle->f_handle;
|
|
p_fh->ns_id = p_id;
|
|
p_fh->ns_type = 0;
|
|
p_fh->ns_inum = 0;
|
|
|
|
c_handle = (struct file_handle *)c_buf;
|
|
c_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
c_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *c_fh = (struct nsfs_file_handle *)c_handle->f_handle;
|
|
c_fh->ns_id = c_id;
|
|
c_fh->ns_type = 0;
|
|
c_fh->ns_inum = 0;
|
|
|
|
/* Open child before process exits */
|
|
int c_fd = open_by_handle_at(FD_NSFS_ROOT, c_handle, O_RDONLY);
|
|
if (c_fd < 0) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open child namespace");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/*
|
|
* With 3-level hierarchy and child active:
|
|
* - Child is active (we hold fd)
|
|
* - Parent should be active (propagated from child)
|
|
* - Grandparent should be active (propagated from parent)
|
|
*/
|
|
TH_LOG("Testing parent active when child is active");
|
|
int p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
|
|
ASSERT_GE(p_fd, 0);
|
|
|
|
TH_LOG("Testing grandparent active when child is active");
|
|
int gp_fd = open_by_handle_at(FD_NSFS_ROOT, gp_handle, O_RDONLY);
|
|
ASSERT_GE(gp_fd, 0);
|
|
|
|
close(c_fd);
|
|
close(p_fd);
|
|
close(gp_fd);
|
|
}
|
|
|
|
/*
|
|
* Test multiple children sharing same parent.
|
|
* Parent should stay active as long as ANY child is active.
|
|
*/
|
|
TEST(ns_multiple_children_same_parent)
|
|
{
|
|
struct file_handle *p_handle, *c1_handle, *c2_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 p_id, c1_id, c2_id;
|
|
char p_buf[sizeof(*p_handle) + MAX_HANDLE_SZ];
|
|
char c1_buf[sizeof(*c1_handle) + MAX_HANDLE_SZ];
|
|
char c2_buf[sizeof(*c2_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
/* Create parent user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int p_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (p_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
|
|
close(p_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(p_fd);
|
|
|
|
/* Create first child user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int c1_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (c1_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(c1_fd, NS_GET_ID, &c1_id) < 0) {
|
|
close(c1_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(c1_fd);
|
|
|
|
/* Return to parent user namespace and create second child */
|
|
/* We can't actually do this easily, so let's create a sibling namespace
|
|
* by creating a network namespace instead */
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int c2_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (c2_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(c2_fd, NS_GET_ID, &c2_id) < 0) {
|
|
close(c2_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(c2_fd);
|
|
|
|
/* Send all namespace IDs */
|
|
write(pipefd[1], &p_id, sizeof(p_id));
|
|
write(pipefd[1], &c1_id, sizeof(c1_id));
|
|
write(pipefd[1], &c2_id, sizeof(c2_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &p_id, sizeof(p_id));
|
|
if (ret != sizeof(p_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read parent namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &c1_id, sizeof(c1_id));
|
|
if (ret != sizeof(c1_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read first child namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &c2_id, sizeof(c2_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(c2_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read second child namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
p_handle = (struct file_handle *)p_buf;
|
|
p_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
p_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)p_handle->f_handle;
|
|
p_fh->ns_id = p_id;
|
|
p_fh->ns_type = 0;
|
|
p_fh->ns_inum = 0;
|
|
|
|
c1_handle = (struct file_handle *)c1_buf;
|
|
c1_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
c1_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *c1_fh = (struct nsfs_file_handle *)c1_handle->f_handle;
|
|
c1_fh->ns_id = c1_id;
|
|
c1_fh->ns_type = 0;
|
|
c1_fh->ns_inum = 0;
|
|
|
|
c2_handle = (struct file_handle *)c2_buf;
|
|
c2_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
c2_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *c2_fh = (struct nsfs_file_handle *)c2_handle->f_handle;
|
|
c2_fh->ns_id = c2_id;
|
|
c2_fh->ns_type = 0;
|
|
c2_fh->ns_inum = 0;
|
|
|
|
/* Open both children before process exits */
|
|
int c1_fd = open_by_handle_at(FD_NSFS_ROOT, c1_handle, O_RDONLY);
|
|
int c2_fd = open_by_handle_at(FD_NSFS_ROOT, c2_handle, O_RDONLY);
|
|
|
|
if (c1_fd < 0 || c2_fd < 0) {
|
|
if (c1_fd >= 0) close(c1_fd);
|
|
if (c2_fd >= 0) close(c2_fd);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open child namespaces");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/* Parent should be active (both children active) */
|
|
TH_LOG("Both children active - parent should be active");
|
|
int p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
|
|
ASSERT_GE(p_fd, 0);
|
|
close(p_fd);
|
|
|
|
/* Close first child - parent should STILL be active */
|
|
TH_LOG("Closing first child - parent should still be active");
|
|
close(c1_fd);
|
|
p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
|
|
ASSERT_GE(p_fd, 0);
|
|
close(p_fd);
|
|
|
|
/* Close second child - NOW parent should become inactive */
|
|
TH_LOG("Closing second child - parent should become inactive");
|
|
close(c2_fd);
|
|
p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
|
|
ASSERT_LT(p_fd, 0);
|
|
}
|
|
|
|
/*
|
|
* Test that different namespace types with same owner all contribute
|
|
* active references to the owning user namespace.
|
|
*/
|
|
TEST(ns_different_types_same_owner)
|
|
{
|
|
struct file_handle *u_handle, *n_handle, *ut_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 u_id, n_id, ut_id;
|
|
char u_buf[sizeof(*u_handle) + MAX_HANDLE_SZ];
|
|
char n_buf[sizeof(*n_handle) + MAX_HANDLE_SZ];
|
|
char ut_buf[sizeof(*ut_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
/* Create user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int u_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (u_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(u_fd, NS_GET_ID, &u_id) < 0) {
|
|
close(u_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(u_fd);
|
|
|
|
/* Create network namespace (owned by user namespace) */
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int n_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (n_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(n_fd, NS_GET_ID, &n_id) < 0) {
|
|
close(n_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(n_fd);
|
|
|
|
/* Create UTS namespace (also owned by user namespace) */
|
|
if (unshare(CLONE_NEWUTS) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ut_fd = open("/proc/self/ns/uts", O_RDONLY);
|
|
if (ut_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ut_fd, NS_GET_ID, &ut_id) < 0) {
|
|
close(ut_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ut_fd);
|
|
|
|
/* Send all namespace IDs */
|
|
write(pipefd[1], &u_id, sizeof(u_id));
|
|
write(pipefd[1], &n_id, sizeof(n_id));
|
|
write(pipefd[1], &ut_id, sizeof(ut_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &u_id, sizeof(u_id));
|
|
if (ret != sizeof(u_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &n_id, sizeof(n_id));
|
|
if (ret != sizeof(n_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read network namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &ut_id, sizeof(ut_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(ut_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read UTS namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
u_handle = (struct file_handle *)u_buf;
|
|
u_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
u_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *u_fh = (struct nsfs_file_handle *)u_handle->f_handle;
|
|
u_fh->ns_id = u_id;
|
|
u_fh->ns_type = 0;
|
|
u_fh->ns_inum = 0;
|
|
|
|
n_handle = (struct file_handle *)n_buf;
|
|
n_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
n_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *n_fh = (struct nsfs_file_handle *)n_handle->f_handle;
|
|
n_fh->ns_id = n_id;
|
|
n_fh->ns_type = 0;
|
|
n_fh->ns_inum = 0;
|
|
|
|
ut_handle = (struct file_handle *)ut_buf;
|
|
ut_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
ut_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ut_fh = (struct nsfs_file_handle *)ut_handle->f_handle;
|
|
ut_fh->ns_id = ut_id;
|
|
ut_fh->ns_type = 0;
|
|
ut_fh->ns_inum = 0;
|
|
|
|
/* Open both non-user namespaces before process exits */
|
|
int n_fd = open_by_handle_at(FD_NSFS_ROOT, n_handle, O_RDONLY);
|
|
int ut_fd = open_by_handle_at(FD_NSFS_ROOT, ut_handle, O_RDONLY);
|
|
|
|
if (n_fd < 0 || ut_fd < 0) {
|
|
if (n_fd >= 0) close(n_fd);
|
|
if (ut_fd >= 0) close(ut_fd);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open namespaces");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/*
|
|
* Both network and UTS namespaces are active.
|
|
* User namespace should be active (gets 2 active refs).
|
|
*/
|
|
TH_LOG("Both net and uts active - user namespace should be active");
|
|
int u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
|
|
ASSERT_GE(u_fd, 0);
|
|
close(u_fd);
|
|
|
|
/* Close network namespace - user namespace should STILL be active */
|
|
TH_LOG("Closing network ns - user ns should still be active (uts still active)");
|
|
close(n_fd);
|
|
u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
|
|
ASSERT_GE(u_fd, 0);
|
|
close(u_fd);
|
|
|
|
/* Close UTS namespace - user namespace should become inactive */
|
|
TH_LOG("Closing uts ns - user ns should become inactive");
|
|
close(ut_fd);
|
|
u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
|
|
ASSERT_LT(u_fd, 0);
|
|
}
|
|
|
|
/*
|
|
* Test hierarchical propagation with deep namespace hierarchy.
|
|
* Create: init_user_ns -> user_A -> user_B -> net_ns
|
|
* When net_ns is active, both user_A and user_B should be active.
|
|
* This verifies the conditional recursion in __ns_ref_active_put() works.
|
|
*/
|
|
TEST(ns_deep_hierarchy_propagation)
|
|
{
|
|
struct file_handle *ua_handle, *ub_handle, *net_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 ua_id, ub_id, net_id;
|
|
char ua_buf[sizeof(*ua_handle) + MAX_HANDLE_SZ];
|
|
char ub_buf[sizeof(*ub_handle) + MAX_HANDLE_SZ];
|
|
char net_buf[sizeof(*net_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
/* Create user_A -> user_B -> net hierarchy */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ua_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (ua_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ua_fd, NS_GET_ID, &ua_id) < 0) {
|
|
close(ua_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ua_fd);
|
|
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ub_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (ub_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ub_fd, NS_GET_ID, &ub_id) < 0) {
|
|
close(ub_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ub_fd);
|
|
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int net_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (net_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(net_fd, NS_GET_ID, &net_id) < 0) {
|
|
close(net_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(net_fd);
|
|
|
|
/* Send all three namespace IDs */
|
|
write(pipefd[1], &ua_id, sizeof(ua_id));
|
|
write(pipefd[1], &ub_id, sizeof(ub_id));
|
|
write(pipefd[1], &net_id, sizeof(net_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &ua_id, sizeof(ua_id));
|
|
if (ret != sizeof(ua_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user_A namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &ub_id, sizeof(ub_id));
|
|
if (ret != sizeof(ub_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user_B namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &net_id, sizeof(net_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(net_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read network namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
ua_handle = (struct file_handle *)ua_buf;
|
|
ua_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
ua_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ua_fh = (struct nsfs_file_handle *)ua_handle->f_handle;
|
|
ua_fh->ns_id = ua_id;
|
|
ua_fh->ns_type = 0;
|
|
ua_fh->ns_inum = 0;
|
|
|
|
ub_handle = (struct file_handle *)ub_buf;
|
|
ub_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
ub_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ub_fh = (struct nsfs_file_handle *)ub_handle->f_handle;
|
|
ub_fh->ns_id = ub_id;
|
|
ub_fh->ns_type = 0;
|
|
ub_fh->ns_inum = 0;
|
|
|
|
net_handle = (struct file_handle *)net_buf;
|
|
net_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
net_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *net_fh = (struct nsfs_file_handle *)net_handle->f_handle;
|
|
net_fh->ns_id = net_id;
|
|
net_fh->ns_type = 0;
|
|
net_fh->ns_inum = 0;
|
|
|
|
/* Open net_ns before child exits to keep it active */
|
|
int net_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY);
|
|
if (net_fd < 0) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open network namespace");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/* With net_ns active, both user_A and user_B should be active */
|
|
TH_LOG("Testing user_B active (net_ns active causes propagation)");
|
|
int ub_fd = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
|
|
ASSERT_GE(ub_fd, 0);
|
|
|
|
TH_LOG("Testing user_A active (propagated through user_B)");
|
|
int ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_GE(ua_fd, 0);
|
|
|
|
/* Close net_ns - user_B should stay active (we hold direct ref) */
|
|
TH_LOG("Closing net_ns, user_B should remain active (direct ref held)");
|
|
close(net_fd);
|
|
int ub_fd2 = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
|
|
ASSERT_GE(ub_fd2, 0);
|
|
close(ub_fd2);
|
|
|
|
/* Close user_B - user_A should stay active (we hold direct ref) */
|
|
TH_LOG("Closing user_B, user_A should remain active (direct ref held)");
|
|
close(ub_fd);
|
|
int ua_fd2 = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_GE(ua_fd2, 0);
|
|
close(ua_fd2);
|
|
|
|
/* Close user_A - everything should become inactive */
|
|
TH_LOG("Closing user_A, all should become inactive");
|
|
close(ua_fd);
|
|
|
|
/* All should now be inactive */
|
|
ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_LT(ua_fd, 0);
|
|
}
|
|
|
|
/*
|
|
* Test that parent stays active as long as ANY child is active.
|
|
* Create parent user namespace with two child net namespaces.
|
|
* Parent should remain active until BOTH children are inactive.
|
|
*/
|
|
TEST(ns_parent_multiple_children_refcount)
|
|
{
|
|
struct file_handle *parent_handle, *net1_handle, *net2_handle;
|
|
int ret, pipefd[2], syncpipe[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 p_id, n1_id, n2_id;
|
|
char p_buf[sizeof(*parent_handle) + MAX_HANDLE_SZ];
|
|
char n1_buf[sizeof(*net1_handle) + MAX_HANDLE_SZ];
|
|
char n2_buf[sizeof(*net2_handle) + MAX_HANDLE_SZ];
|
|
char sync_byte;
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
ASSERT_EQ(pipe(syncpipe), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
close(syncpipe[1]);
|
|
|
|
/* Create parent user namespace */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int p_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (p_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
|
|
close(p_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(p_fd);
|
|
|
|
/* Create first network namespace */
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
|
|
int n1_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (n1_fd < 0) {
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(n1_fd, NS_GET_ID, &n1_id) < 0) {
|
|
close(n1_fd);
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
/* Keep n1_fd open so first namespace stays active */
|
|
|
|
/* Create second network namespace */
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(n1_fd);
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
|
|
int n2_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (n2_fd < 0) {
|
|
close(n1_fd);
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(n2_fd, NS_GET_ID, &n2_id) < 0) {
|
|
close(n1_fd);
|
|
close(n2_fd);
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
exit(1);
|
|
}
|
|
/* Keep both n1_fd and n2_fd open */
|
|
|
|
/* Send all namespace IDs */
|
|
write(pipefd[1], &p_id, sizeof(p_id));
|
|
write(pipefd[1], &n1_id, sizeof(n1_id));
|
|
write(pipefd[1], &n2_id, sizeof(n2_id));
|
|
close(pipefd[1]);
|
|
|
|
/* Wait for parent to signal before exiting */
|
|
read(syncpipe[0], &sync_byte, 1);
|
|
close(syncpipe[0]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
close(syncpipe[0]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &p_id, sizeof(p_id));
|
|
if (ret != sizeof(p_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read parent namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &n1_id, sizeof(n1_id));
|
|
if (ret != sizeof(n1_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read first network namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &n2_id, sizeof(n2_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(n2_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read second network namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
parent_handle = (struct file_handle *)p_buf;
|
|
parent_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
parent_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)parent_handle->f_handle;
|
|
p_fh->ns_id = p_id;
|
|
p_fh->ns_type = 0;
|
|
p_fh->ns_inum = 0;
|
|
|
|
net1_handle = (struct file_handle *)n1_buf;
|
|
net1_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
net1_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *n1_fh = (struct nsfs_file_handle *)net1_handle->f_handle;
|
|
n1_fh->ns_id = n1_id;
|
|
n1_fh->ns_type = 0;
|
|
n1_fh->ns_inum = 0;
|
|
|
|
net2_handle = (struct file_handle *)n2_buf;
|
|
net2_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
net2_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *n2_fh = (struct nsfs_file_handle *)net2_handle->f_handle;
|
|
n2_fh->ns_id = n2_id;
|
|
n2_fh->ns_type = 0;
|
|
n2_fh->ns_inum = 0;
|
|
|
|
/* Open both net namespaces while child is still alive */
|
|
int n1_fd = open_by_handle_at(FD_NSFS_ROOT, net1_handle, O_RDONLY);
|
|
int n2_fd = open_by_handle_at(FD_NSFS_ROOT, net2_handle, O_RDONLY);
|
|
if (n1_fd < 0 || n2_fd < 0) {
|
|
if (n1_fd >= 0) close(n1_fd);
|
|
if (n2_fd >= 0) close(n2_fd);
|
|
sync_byte = 'G';
|
|
write(syncpipe[1], &sync_byte, 1);
|
|
close(syncpipe[1]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open net namespaces");
|
|
}
|
|
|
|
/* Signal child that we have opened the namespaces */
|
|
sync_byte = 'G';
|
|
write(syncpipe[1], &sync_byte, 1);
|
|
close(syncpipe[1]);
|
|
|
|
/* Wait for child to exit */
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/* Parent should be active (has 2 active children) */
|
|
TH_LOG("Both net namespaces active - parent should be active");
|
|
int p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_GE(p_fd, 0);
|
|
close(p_fd);
|
|
|
|
/* Close first net namespace - parent should STILL be active */
|
|
TH_LOG("Closing first net ns - parent should still be active");
|
|
close(n1_fd);
|
|
p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_GE(p_fd, 0);
|
|
close(p_fd);
|
|
|
|
/* Close second net namespace - parent should become inactive */
|
|
TH_LOG("Closing second net ns - parent should become inactive");
|
|
close(n2_fd);
|
|
p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
|
|
ASSERT_LT(p_fd, 0);
|
|
}
|
|
|
|
/*
|
|
* Test that user namespace as a child also propagates correctly.
|
|
* Create user_A -> user_B, verify when user_B is active that user_A
|
|
* is also active. This is different from non-user namespace children.
|
|
*/
|
|
TEST(ns_userns_child_propagation)
|
|
{
|
|
struct file_handle *ua_handle, *ub_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 ua_id, ub_id;
|
|
char ua_buf[sizeof(*ua_handle) + MAX_HANDLE_SZ];
|
|
char ub_buf[sizeof(*ub_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
/* Create user_A */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ua_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (ua_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ua_fd, NS_GET_ID, &ua_id) < 0) {
|
|
close(ua_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ua_fd);
|
|
|
|
/* Create user_B (child of user_A) */
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ub_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (ub_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ub_fd, NS_GET_ID, &ub_id) < 0) {
|
|
close(ub_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ub_fd);
|
|
|
|
/* Send both namespace IDs */
|
|
write(pipefd[1], &ua_id, sizeof(ua_id));
|
|
write(pipefd[1], &ub_id, sizeof(ub_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read both namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &ua_id, sizeof(ua_id));
|
|
if (ret != sizeof(ua_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user_A namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &ub_id, sizeof(ub_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(ub_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user_B namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
ua_handle = (struct file_handle *)ua_buf;
|
|
ua_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
ua_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ua_fh = (struct nsfs_file_handle *)ua_handle->f_handle;
|
|
ua_fh->ns_id = ua_id;
|
|
ua_fh->ns_type = 0;
|
|
ua_fh->ns_inum = 0;
|
|
|
|
ub_handle = (struct file_handle *)ub_buf;
|
|
ub_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
ub_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ub_fh = (struct nsfs_file_handle *)ub_handle->f_handle;
|
|
ub_fh->ns_id = ub_id;
|
|
ub_fh->ns_type = 0;
|
|
ub_fh->ns_inum = 0;
|
|
|
|
/* Open user_B before child exits */
|
|
int ub_fd = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
|
|
if (ub_fd < 0) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open user_B");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/* With user_B active, user_A should also be active */
|
|
TH_LOG("Testing user_A active when child user_B is active");
|
|
int ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_GE(ua_fd, 0);
|
|
|
|
/* Close user_B */
|
|
TH_LOG("Closing user_B");
|
|
close(ub_fd);
|
|
|
|
/* user_A should remain active (we hold direct ref) */
|
|
int ua_fd2 = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_GE(ua_fd2, 0);
|
|
close(ua_fd2);
|
|
|
|
/* Close user_A - should become inactive */
|
|
TH_LOG("Closing user_A - should become inactive");
|
|
close(ua_fd);
|
|
|
|
ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
|
|
ASSERT_LT(ua_fd, 0);
|
|
}
|
|
|
|
/*
|
|
* Test different namespace types (net, uts, ipc) all contributing
|
|
* active references to the same owning user namespace.
|
|
*/
|
|
TEST(ns_mixed_types_same_owner)
|
|
{
|
|
struct file_handle *user_handle, *net_handle, *uts_handle;
|
|
int ret, pipefd[2];
|
|
pid_t pid;
|
|
int status;
|
|
__u64 u_id, n_id, ut_id;
|
|
char u_buf[sizeof(*user_handle) + MAX_HANDLE_SZ];
|
|
char n_buf[sizeof(*net_handle) + MAX_HANDLE_SZ];
|
|
char ut_buf[sizeof(*uts_handle) + MAX_HANDLE_SZ];
|
|
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
pid = fork();
|
|
ASSERT_GE(pid, 0);
|
|
|
|
if (pid == 0) {
|
|
close(pipefd[0]);
|
|
|
|
if (setup_userns() < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int u_fd = open("/proc/self/ns/user", O_RDONLY);
|
|
if (u_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(u_fd, NS_GET_ID, &u_id) < 0) {
|
|
close(u_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(u_fd);
|
|
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int n_fd = open("/proc/self/ns/net", O_RDONLY);
|
|
if (n_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(n_fd, NS_GET_ID, &n_id) < 0) {
|
|
close(n_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(n_fd);
|
|
|
|
if (unshare(CLONE_NEWUTS) < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
int ut_fd = open("/proc/self/ns/uts", O_RDONLY);
|
|
if (ut_fd < 0) {
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
if (ioctl(ut_fd, NS_GET_ID, &ut_id) < 0) {
|
|
close(ut_fd);
|
|
close(pipefd[1]);
|
|
exit(1);
|
|
}
|
|
close(ut_fd);
|
|
|
|
/* Send all namespace IDs */
|
|
write(pipefd[1], &u_id, sizeof(u_id));
|
|
write(pipefd[1], &n_id, sizeof(n_id));
|
|
write(pipefd[1], &ut_id, sizeof(ut_id));
|
|
close(pipefd[1]);
|
|
exit(0);
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* Read all three namespace IDs - fixed size, no parsing needed */
|
|
ret = read(pipefd[0], &u_id, sizeof(u_id));
|
|
if (ret != sizeof(u_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read user namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &n_id, sizeof(n_id));
|
|
if (ret != sizeof(n_id)) {
|
|
close(pipefd[0]);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read network namespace ID");
|
|
}
|
|
|
|
ret = read(pipefd[0], &ut_id, sizeof(ut_id));
|
|
close(pipefd[0]);
|
|
if (ret != sizeof(ut_id)) {
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to read UTS namespace ID");
|
|
}
|
|
|
|
/* Construct file handles from namespace IDs */
|
|
user_handle = (struct file_handle *)u_buf;
|
|
user_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
user_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *u_fh = (struct nsfs_file_handle *)user_handle->f_handle;
|
|
u_fh->ns_id = u_id;
|
|
u_fh->ns_type = 0;
|
|
u_fh->ns_inum = 0;
|
|
|
|
net_handle = (struct file_handle *)n_buf;
|
|
net_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
net_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *n_fh = (struct nsfs_file_handle *)net_handle->f_handle;
|
|
n_fh->ns_id = n_id;
|
|
n_fh->ns_type = 0;
|
|
n_fh->ns_inum = 0;
|
|
|
|
uts_handle = (struct file_handle *)ut_buf;
|
|
uts_handle->handle_bytes = sizeof(struct nsfs_file_handle);
|
|
uts_handle->handle_type = FILEID_NSFS;
|
|
struct nsfs_file_handle *ut_fh = (struct nsfs_file_handle *)uts_handle->f_handle;
|
|
ut_fh->ns_id = ut_id;
|
|
ut_fh->ns_type = 0;
|
|
ut_fh->ns_inum = 0;
|
|
|
|
/* Open both non-user namespaces */
|
|
int n_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY);
|
|
int ut_fd = open_by_handle_at(FD_NSFS_ROOT, uts_handle, O_RDONLY);
|
|
if (n_fd < 0 || ut_fd < 0) {
|
|
if (n_fd >= 0) close(n_fd);
|
|
if (ut_fd >= 0) close(ut_fd);
|
|
waitpid(pid, NULL, 0);
|
|
SKIP(return, "Failed to open namespaces");
|
|
}
|
|
|
|
waitpid(pid, &status, 0);
|
|
ASSERT_TRUE(WIFEXITED(status));
|
|
ASSERT_EQ(WEXITSTATUS(status), 0);
|
|
|
|
/* User namespace should be active (2 active children) */
|
|
TH_LOG("Both net and uts active - user ns should be active");
|
|
int u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
|
|
ASSERT_GE(u_fd, 0);
|
|
close(u_fd);
|
|
|
|
/* Close net - user ns should STILL be active (uts still active) */
|
|
TH_LOG("Closing net - user ns should still be active");
|
|
close(n_fd);
|
|
u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
|
|
ASSERT_GE(u_fd, 0);
|
|
close(u_fd);
|
|
|
|
/* Close uts - user ns should become inactive */
|
|
TH_LOG("Closing uts - user ns should become inactive");
|
|
close(ut_fd);
|
|
u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
|
|
ASSERT_LT(u_fd, 0);
|
|
}
|
|
|
|
TEST_HARNESS_MAIN
|