mirror of https://github.com/torvalds/linux.git
selftests/namespaces: seventh active reference count tests
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. Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-29-2e6f823ebdc0@kernel.org Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
parent
47a5fd8ce1
commit
4b971b07e4
|
|
@ -20,6 +20,10 @@
|
|||
#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.
|
||||
|
|
@ -603,4 +607,222 @@ TEST(ns_fd_keeps_active)
|
|||
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_HARNESS_MAIN
|
||||
|
|
|
|||
Loading…
Reference in New Issue