]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/namespaces: seventh active reference count tests
authorChristian Brauner <brauner@kernel.org>
Wed, 29 Oct 2025 12:20:42 +0000 (13:20 +0100)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Nov 2025 16:41:19 +0000 (17:41 +0100)
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>
tools/testing/selftests/namespaces/ns_active_ref_test.c

index 50653096fcb69fd06751edb6399a657d0976f955..60876965dd71c21cbba88dc0e5bb1b6710a917c3 100644 (file)
 #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