| // SPDX-License-Identifier: GPL-2.0 |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| #include "../pidfd/pidfd.h" |
| #include "../kselftest_harness.h" |
| |
| /* |
| * Regression tests for the setns(pidfd) active reference counting bug. |
| * |
| * These tests are based on the reproducers that triggered the race condition |
| * fixed by commit 1c465d0518dc ("ns: handle setns(pidfd, ...) cleanly"). |
| * |
| * The bug: When using setns() with a pidfd, if the target task exits between |
| * prepare_nsset() and commit_nsset(), the namespaces would become inactive. |
| * Then ns_ref_active_get() would increment from 0 without properly resurrecting |
| * the owner chain, causing active reference count underflows. |
| */ |
| |
| /* |
| * Simple pidfd setns test using create_child()+unshare(). |
| * |
| * Without the fix, this would trigger active refcount warnings when the |
| * parent exits after doing setns(pidfd) on a child that has already exited. |
| */ |
| TEST(simple_pidfd_setns) |
| { |
| pid_t child_pid; |
| int pidfd = -1; |
| int ret; |
| int sv[2]; |
| char c; |
| |
| /* Ignore SIGCHLD for autoreap */ |
| ASSERT_NE(signal(SIGCHLD, SIG_IGN), SIG_ERR); |
| |
| ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); |
| |
| /* Create a child process without namespaces initially */ |
| child_pid = create_child(&pidfd, 0); |
| ASSERT_GE(child_pid, 0); |
| |
| if (child_pid == 0) { |
| close(sv[0]); |
| |
| if (unshare(CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUSER) < 0) { |
| close(sv[1]); |
| _exit(1); |
| } |
| |
| /* Signal parent that namespaces are ready */ |
| if (write_nointr(sv[1], "1", 1) < 0) { |
| close(sv[1]); |
| _exit(1); |
| } |
| |
| close(sv[1]); |
| _exit(0); |
| } |
| ASSERT_GE(pidfd, 0); |
| EXPECT_EQ(close(sv[1]), 0); |
| |
| ret = read_nointr(sv[0], &c, 1); |
| ASSERT_EQ(ret, 1); |
| EXPECT_EQ(close(sv[0]), 0); |
| |
| /* Set to child's namespaces via pidfd */ |
| ret = setns(pidfd, CLONE_NEWUTS | CLONE_NEWIPC); |
| TH_LOG("setns() returned %d", ret); |
| close(pidfd); |
| } |
| |
| /* |
| * Simple pidfd setns test using create_child(). |
| * |
| * This variation uses create_child() with namespace flags directly. |
| * Namespaces are created immediately at clone time. |
| */ |
| TEST(simple_pidfd_setns_clone) |
| { |
| pid_t child_pid; |
| int pidfd = -1; |
| int ret; |
| |
| /* Ignore SIGCHLD for autoreap */ |
| ASSERT_NE(signal(SIGCHLD, SIG_IGN), SIG_ERR); |
| |
| /* Create a child process with new namespaces using create_child() */ |
| child_pid = create_child(&pidfd, CLONE_NEWUSER | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET); |
| ASSERT_GE(child_pid, 0); |
| |
| if (child_pid == 0) { |
| /* Child: sleep for a while so parent can setns to us */ |
| sleep(2); |
| _exit(0); |
| } |
| |
| /* Parent: pidfd was already created by create_child() */ |
| ASSERT_GE(pidfd, 0); |
| |
| /* Set to child's namespaces via pidfd */ |
| ret = setns(pidfd, CLONE_NEWUTS | CLONE_NEWIPC); |
| close(pidfd); |
| TH_LOG("setns() returned %d", ret); |
| } |
| |
| TEST_HARNESS_MAIN |