* [PATCH] selftests: pid_namespace: test fork with dead ancestor
@ 2026-10-07 11:49 Sahaj Chaudhari
2026-10-12 9:06 ` Christian Brauner
0 siblings, 1 reply; 2+ messages in thread
From: Sahaj Chaudhari @ 2026-10-07 11:49 UTC (permalink / raw)
To: Christian Brauner, Shuah Khan
Cc: Pavel Tikhomirov, linux-kernel, linux-kselftest, Sahaj Chaudhari
Add a regression test for fork() into a PID namespace whose parent
namespace has lost its init process. Since the kernel cannot assign the
new process a PID in that dead ancestor, fork() must fail with ENOMEM.
Create the nested PID namespaces, keep the child namespace handle open
after its init process exits, then enter it and verify fork() fails.
Register the test with the PID namespace selftests and ignore its
generated binary. Skip when namespace operations are denied or procfs
cannot provide the pid_for_children handle.
Signed-off-by: Sahaj Chaudhari <sahaj123.sc@gmail.com>
---
.../selftests/pid_namespace/.gitignore | 1 +
.../testing/selftests/pid_namespace/Makefile | 2 +-
.../pid_namespace/pidns_dead_ancestor.c | 174 ++++++++++++++++++
3 files changed, 176 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c
diff --git a/tools/testing/selftests/pid_namespace/.gitignore b/tools/testing/selftests/pid_namespace/.gitignore
index c647c6eb33672..b87295802a73c 100644
--- a/tools/testing/selftests/pid_namespace/.gitignore
+++ b/tools/testing/selftests/pid_namespace/.gitignore
@@ -1,3 +1,4 @@
pid_max
+pidns_dead_ancestor
pidns_init_via_setns
regression_enomem
diff --git a/tools/testing/selftests/pid_namespace/Makefile b/tools/testing/selftests/pid_namespace/Makefile
index b01a924ac04bd..3199e6bebbdd8 100644
--- a/tools/testing/selftests/pid_namespace/Makefile
+++ b/tools/testing/selftests/pid_namespace/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -g $(KHDR_INCLUDES)
-TEST_GEN_PROGS = regression_enomem pid_max pidns_init_via_setns
+TEST_GEN_PROGS = regression_enomem pid_max pidns_dead_ancestor pidns_init_via_setns
LOCAL_HDRS += $(selfdir)/pidfd/pidfd.h
diff --git a/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c b/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c
new file mode 100644
index 0000000000000..7524d4975ec75
--- /dev/null
+++ b/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+#define STACK_SIZE (1024 * 1024)
+
+struct pidns_init_args {
+ int ready_read;
+ int ready_write;
+ int release_read;
+ int release_write;
+};
+
+static int pidns_init(void *data)
+{
+ struct pidns_init_args *args = data;
+ char release;
+ int error = 0;
+
+ close(args->ready_read);
+ close(args->release_write);
+ if (unshare(CLONE_NEWPID))
+ error = errno;
+ if (write(args->ready_write, &error, sizeof(error)) != sizeof(error))
+ return 1;
+ close(args->ready_write);
+ if (error)
+ return 0;
+ if (read(args->release_read, &release, sizeof(release)) != sizeof(release))
+ return 1;
+ close(args->release_read);
+ return 0;
+}
+
+static int create_unborn_pidns(void)
+{
+ struct pidns_init_args args;
+ char path[64], release = 0;
+ void *stack;
+ pid_t child;
+ int ready[2], release_pipe[2], error, status, nsfd = -1;
+ ssize_t n;
+
+ if (pipe(ready))
+ return -1;
+ if (pipe(release_pipe)) {
+ error = errno;
+ close(ready[0]);
+ close(ready[1]);
+ errno = error;
+ return -1;
+ }
+
+ stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED) {
+ error = errno;
+ close(ready[0]);
+ close(ready[1]);
+ close(release_pipe[0]);
+ close(release_pipe[1]);
+ errno = error;
+ return -1;
+ }
+
+ args = (struct pidns_init_args) {
+ .ready_read = ready[0],
+ .ready_write = ready[1],
+ .release_read = release_pipe[0],
+ .release_write = release_pipe[1],
+ };
+ child = clone(pidns_init, stack + STACK_SIZE, CLONE_NEWPID | SIGCHLD,
+ &args);
+ if (child < 0)
+ goto err_munmap;
+
+ close(ready[1]);
+ close(release_pipe[0]);
+ n = read(ready[0], &error, sizeof(error));
+ if (n != sizeof(error)) {
+ error = n < 0 ? errno : EIO;
+ goto release_child;
+ }
+ if (error)
+ goto release_child;
+
+ snprintf(path, sizeof(path), "/proc/%d/ns/pid_for_children", child);
+ nsfd = open(path, O_RDONLY | O_CLOEXEC);
+ if (nsfd < 0) {
+ error = errno;
+ goto release_child;
+ }
+
+release_child:
+ write(release_pipe[1], &release, sizeof(release));
+ close(release_pipe[1]);
+ close(ready[0]);
+ if (waitpid(child, &status, 0) != child || !WIFEXITED(status) ||
+ WEXITSTATUS(status)) {
+ if (!error)
+ error = ECHILD;
+ if (nsfd >= 0)
+ close(nsfd);
+ nsfd = -1;
+ }
+ munmap(stack, STACK_SIZE);
+ if (error) {
+ errno = error;
+ return -1;
+ }
+ return nsfd;
+
+err_munmap:
+ error = errno;
+ munmap(stack, STACK_SIZE);
+ close(ready[0]);
+ close(ready[1]);
+ close(release_pipe[0]);
+ close(release_pipe[1]);
+ errno = error;
+ return -1;
+}
+
+TEST(pidns_dead_ancestor_rejects_fork)
+{
+ int original_fd, target_fd, fork_errno, ret;
+ pid_t child;
+
+ target_fd = create_unborn_pidns();
+ if (target_fd < 0) {
+ if (errno == ENOENT)
+ SKIP(return, "PID namespace handle unavailable through procfs\n");
+ if (errno == EPERM || errno == EACCES || errno == EINVAL)
+ SKIP(return, "PID namespace creation requires privileges\n");
+ ASSERT_GE(target_fd, 0);
+ }
+
+ original_fd = open("/proc/self/ns/pid_for_children", O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(original_fd, 0);
+ ret = setns(target_fd, CLONE_NEWPID);
+ if (ret) {
+ if (errno == EPERM || errno == EACCES)
+ SKIP(return, "setns into PID namespace requires privileges\n");
+ }
+ ASSERT_EQ(0, ret);
+
+ child = fork();
+ fork_errno = errno;
+ if (child == 0)
+ _exit(0);
+
+ if (child > 0)
+ ASSERT_EQ(child, waitpid(child, NULL, 0));
+ ret = setns(original_fd, CLONE_NEWPID);
+ close(original_fd);
+ close(target_fd);
+ ASSERT_EQ(0, ret);
+
+ ASSERT_EQ(-1, child);
+ ASSERT_EQ(ENOMEM, fork_errno);
+}
+
+TEST_HARNESS_MAIN
^ permalink raw reply [flat|nested] 2+ messages in thread* Re: [PATCH] selftests: pid_namespace: test fork with dead ancestor
2026-10-07 11:49 [PATCH] selftests: pid_namespace: test fork with dead ancestor Sahaj Chaudhari
@ 2026-10-12 9:06 ` Christian Brauner
0 siblings, 0 replies; 2+ messages in thread
From: Christian Brauner @ 2026-10-12 9:06 UTC (permalink / raw)
To: Shuah Khan, Sahaj Chaudhari
Cc: Pavel Tikhomirov, linux-kernel, linux-kselftest
On Wed, 07 Oct 2026 17:19:14 +0530, Sahaj Chaudhari wrote:
> Add a regression test for fork() into a PID namespace whose parent
> namespace has lost its init process. Since the kernel cannot assign the
> new process a PID in that dead ancestor, fork() must fail with ENOMEM.
>
> Create the nested PID namespaces, keep the child namespace handle open
> after its init process exits, then enter it and verify fork() fails.
>
> [...]
Applied to the kernel-7.4.signal branch of the vfs/vfs.git tree.
Patches in the kernel-7.4.signal branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: kernel-7.4.signal
[1/1] selftests: pid_namespace: test fork with dead ancestor
https://git.kernel.org/vfs/vfs/c/2ec5a0709461
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-12 9:06 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-07 11:49 [PATCH] selftests: pid_namespace: test fork with dead ancestor Sahaj Chaudhari
2026-10-12 9:06 ` Christian Brauner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®