]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/pidfd: add CLONE_NNP tests
authorChristian Brauner <brauner@kernel.org>
Thu, 26 Feb 2026 13:51:03 +0000 (14:51 +0100)
committerChristian Brauner <brauner@kernel.org>
Wed, 11 Mar 2026 22:23:31 +0000 (23:23 +0100)
Add tests for the new CLONE_NNP flag:

- nnp_sets_no_new_privs: Verify a child created with CLONE_NNP has
  no_new_privs set while the parent does not.

- nnp_rejects_thread: Verify CLONE_NNP | CLONE_THREAD is rejected
  with -EINVAL since threads share credentials.

- autoreap_no_new_privs_unset: Verify a plain CLONE_AUTOREAP child
  does not get no_new_privs.

Link: https://patch.msgid.link/20260226-work-pidfs-autoreap-v5-5-d148b984a989@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
tools/testing/selftests/pidfd/pidfd_autoreap_test.c

index 22bdc04c7dd0b79317ec1d96731f14916b7a96d4..d1f3882f7d6e4f7ca4a6fb8a2dd7c63d39fdd545 100644 (file)
 #define CLONE_AUTOREAP (1ULL << 34)
 #endif
 
+#ifndef CLONE_NNP
+#define CLONE_NNP (1ULL << 35)
+#endif
+
 static pid_t create_autoreap_child(int *pidfd)
 {
        struct __clone_args args = {
@@ -493,4 +497,126 @@ TEST(autoreap_no_inherit)
        close(pidfd);
 }
 
+/*
+ * Test that CLONE_NNP sets no_new_privs on the child.
+ * The child checks via prctl(PR_GET_NO_NEW_PRIVS) and reports back.
+ * The parent must NOT have no_new_privs set afterwards.
+ */
+TEST(nnp_sets_no_new_privs)
+{
+       struct __clone_args args = {
+               .flags          = CLONE_PIDFD | CLONE_AUTOREAP | CLONE_NNP,
+               .exit_signal    = 0,
+       };
+       struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
+       int pidfd = -1, ret;
+       struct pollfd pfd;
+       pid_t pid;
+
+       /* Ensure parent does not already have no_new_privs. */
+       ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+       ASSERT_EQ(ret, 0) {
+               TH_LOG("Parent already has no_new_privs set, cannot run test");
+       }
+
+       args.pidfd = ptr_to_u64(&pidfd);
+
+       pid = sys_clone3(&args, sizeof(args));
+       if (pid < 0 && errno == EINVAL)
+               SKIP(return, "CLONE_NNP not supported");
+       ASSERT_GE(pid, 0);
+
+       if (pid == 0) {
+               /*
+                * Child: check no_new_privs. Exit 0 if set, 1 if not.
+                */
+               ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+               _exit(ret == 1 ? 0 : 1);
+       }
+
+       ASSERT_GE(pidfd, 0);
+
+       /* Parent must still NOT have no_new_privs. */
+       ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+       ASSERT_EQ(ret, 0) {
+               TH_LOG("Parent got no_new_privs after creating CLONE_NNP child");
+       }
+
+       /* Wait for child to exit. */
+       pfd.fd = pidfd;
+       pfd.events = POLLIN;
+       ret = poll(&pfd, 1, 5000);
+       ASSERT_EQ(ret, 1);
+
+       /* Verify child exited with 0 (no_new_privs was set). */
+       ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
+       ASSERT_EQ(ret, 0);
+       ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
+       ASSERT_TRUE(WIFEXITED(info.exit_code));
+       ASSERT_EQ(WEXITSTATUS(info.exit_code), 0) {
+               TH_LOG("Child did not have no_new_privs set");
+       }
+
+       close(pidfd);
+}
+
+/*
+ * Test that CLONE_NNP with CLONE_THREAD fails with EINVAL.
+ */
+TEST(nnp_rejects_thread)
+{
+       struct __clone_args args = {
+               .flags          = CLONE_NNP | CLONE_THREAD |
+                                 CLONE_SIGHAND | CLONE_VM,
+               .exit_signal    = 0,
+       };
+       pid_t pid;
+
+       pid = sys_clone3(&args, sizeof(args));
+       ASSERT_EQ(pid, -1);
+       ASSERT_EQ(errno, EINVAL);
+}
+
+/*
+ * Test that a plain CLONE_AUTOREAP child does NOT get no_new_privs.
+ * Only CLONE_NNP should set it.
+ */
+TEST(autoreap_no_new_privs_unset)
+{
+       struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
+       int pidfd = -1, ret;
+       struct pollfd pfd;
+       pid_t pid;
+
+       pid = create_autoreap_child(&pidfd);
+       if (pid < 0 && errno == EINVAL)
+               SKIP(return, "CLONE_AUTOREAP not supported");
+       ASSERT_GE(pid, 0);
+
+       if (pid == 0) {
+               /*
+                * Child: check no_new_privs. Exit 0 if NOT set, 1 if set.
+                */
+               ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+               _exit(ret == 0 ? 0 : 1);
+       }
+
+       ASSERT_GE(pidfd, 0);
+
+       pfd.fd = pidfd;
+       pfd.events = POLLIN;
+       ret = poll(&pfd, 1, 5000);
+       ASSERT_EQ(ret, 1);
+
+       ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
+       ASSERT_EQ(ret, 0);
+       ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
+       ASSERT_TRUE(WIFEXITED(info.exit_code));
+       ASSERT_EQ(WEXITSTATUS(info.exit_code), 0) {
+               TH_LOG("Plain autoreap child unexpectedly has no_new_privs");
+       }
+
+       close(pidfd);
+}
+
 TEST_HARNESS_MAIN