summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/pid_namespace
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/pid_namespace')
-rw-r--r--tools/testing/selftests/pid_namespace/.gitignore1
-rw-r--r--tools/testing/selftests/pid_namespace/Makefile2
-rw-r--r--tools/testing/selftests/pid_namespace/pid_max.c156
-rw-r--r--tools/testing/selftests/pid_namespace/pidns_init_via_setns.c238
4 files changed, 364 insertions, 33 deletions
diff --git a/tools/testing/selftests/pid_namespace/.gitignore b/tools/testing/selftests/pid_namespace/.gitignore
index 5118f0f3edf4..c647c6eb3367 100644
--- a/tools/testing/selftests/pid_namespace/.gitignore
+++ b/tools/testing/selftests/pid_namespace/.gitignore
@@ -1,2 +1,3 @@
pid_max
+pidns_init_via_setns
regression_enomem
diff --git a/tools/testing/selftests/pid_namespace/Makefile b/tools/testing/selftests/pid_namespace/Makefile
index b972f55d07ae..b01a924ac04b 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
+TEST_GEN_PROGS = regression_enomem pid_max pidns_init_via_setns
LOCAL_HDRS += $(selfdir)/pidfd/pidfd.h
diff --git a/tools/testing/selftests/pid_namespace/pid_max.c b/tools/testing/selftests/pid_namespace/pid_max.c
index c9519e7385b6..5d686a09aa15 100644
--- a/tools/testing/selftests/pid_namespace/pid_max.c
+++ b/tools/testing/selftests/pid_namespace/pid_max.c
@@ -12,10 +12,74 @@
#include <syscall.h>
#include <sys/mount.h>
#include <sys/wait.h>
+#include <unistd.h>
#include "kselftest_harness.h"
#include "../pidfd/pidfd.h"
+/*
+ * The kernel computes the minimum allowed pid_max as:
+ * max(RESERVED_PIDS + 1, PIDS_PER_CPU_MIN * num_possible_cpus())
+ * Mirror that here so the test values are always valid.
+ *
+ * Note: glibc's get_nprocs_conf() returns the number of *configured*
+ * (present) CPUs, not *possible* CPUs. The kernel uses
+ * num_possible_cpus() which corresponds to /sys/devices/system/cpu/possible.
+ * These can differ significantly (e.g. 16 configured vs 128 possible).
+ */
+#define RESERVED_PIDS 300
+#define PIDS_PER_CPU_MIN 8
+
+/* Count CPUs from a range list like "0-31" or "0-15,32-47". */
+static int num_possible_cpus(void)
+{
+ FILE *f;
+ int count = 0;
+ int lo, hi;
+
+ f = fopen("/sys/devices/system/cpu/possible", "r");
+ if (!f)
+ return 0;
+
+ while (fscanf(f, "%d", &lo) == 1) {
+ if (fscanf(f, "-%d", &hi) == 1)
+ count += hi - lo + 1;
+ else
+ count++;
+ /* skip comma separator */
+ fscanf(f, ",");
+ }
+
+ fclose(f);
+ return count;
+}
+
+static int pid_min(void)
+{
+ int cpu_min = PIDS_PER_CPU_MIN * num_possible_cpus();
+
+ return cpu_min > (RESERVED_PIDS + 1) ? cpu_min : (RESERVED_PIDS + 1);
+}
+
+/*
+ * Outer and inner pid_max limits used by the tests. The outer limit is
+ * the more restrictive ancestor; the inner limit is set higher in a
+ * nested namespace but must still be capped by the outer limit.
+ * Both are derived from the kernel's minimum so they are always writable.
+ *
+ * Global so that clone callbacks can access them without parameter plumbing.
+ */
+static int outer_limit;
+static int inner_limit;
+
+static int write_int_to_fd(int fd, int val)
+{
+ char buf[12];
+ int len = snprintf(buf, sizeof(buf), "%d", val);
+
+ return write(fd, buf, len);
+}
+
#define __STACK_SIZE (8 * 1024 * 1024)
static pid_t do_clone(int (*fn)(void *), void *arg, int flags)
{
@@ -60,18 +124,18 @@ static int pid_max_cb(void *data)
return -1;
}
- ret = write(fd, "500", sizeof("500") - 1);
+ ret = write_int_to_fd(fd, inner_limit);
if (ret < 0) {
fprintf(stderr, "%m - Failed to write pid_max\n");
return -1;
}
- for (int i = 0; i < 501; i++) {
+ for (int i = 0; i < inner_limit + 1; i++) {
pid = fork();
if (pid == 0)
exit(EXIT_SUCCESS);
wait_for_pid(pid);
- if (pid > 500) {
+ if (pid > inner_limit) {
fprintf(stderr, "Managed to create pid number beyond limit\n");
return -1;
}
@@ -106,7 +170,7 @@ static int pid_max_nested_inner(void *data)
return fret;
}
- ret = write(fd, "500", sizeof("500") - 1);
+ ret = write_int_to_fd(fd, inner_limit);
close(fd);
if (ret < 0) {
fprintf(stderr, "%m - Failed to write pid_max\n");
@@ -133,8 +197,8 @@ static int pid_max_nested_inner(void *data)
return fret;
}
- /* Now make sure that we wrap pids at 400. */
- for (i = 0; i < 510; i++) {
+ /* Now make sure that we wrap pids at outer_limit. */
+ for (i = 0; i < inner_limit + 10; i++) {
pid_t pid;
pid = fork();
@@ -145,7 +209,7 @@ static int pid_max_nested_inner(void *data)
exit(EXIT_SUCCESS);
wait_for_pid(pid);
- if (pid >= 500) {
+ if (pid >= inner_limit) {
fprintf(stderr, "Managed to create process with pid %d beyond configured limit\n", pid);
return fret;
}
@@ -156,15 +220,19 @@ static int pid_max_nested_inner(void *data)
static int pid_max_nested_outer(void *data)
{
- int fret = -1, nr_procs = 400;
- pid_t pids[1000];
- int fd, i, ret;
+ int fret = -1, nr_procs = 0;
+ pid_t *pids;
+ int fd, ret;
pid_t pid;
+ pids = malloc(outer_limit * sizeof(pid_t));
+ if (!pids)
+ return -1;
+
ret = mount("", "/", NULL, MS_PRIVATE | MS_REC, 0);
if (ret) {
fprintf(stderr, "%m - Failed to make rootfs private mount\n");
- return fret;
+ goto out;
}
umount2("/proc", MNT_DETACH);
@@ -172,27 +240,28 @@ static int pid_max_nested_outer(void *data)
ret = mount("proc", "/proc", "proc", 0, NULL);
if (ret) {
fprintf(stderr, "%m - Failed to mount proc\n");
- return fret;
+ goto out;
}
fd = open("/proc/sys/kernel/pid_max", O_RDWR | O_CLOEXEC | O_NOCTTY);
if (fd < 0) {
fprintf(stderr, "%m - Failed to open pid_max\n");
- return fret;
+ goto out;
}
- ret = write(fd, "400", sizeof("400") - 1);
+ ret = write_int_to_fd(fd, outer_limit);
close(fd);
if (ret < 0) {
fprintf(stderr, "%m - Failed to write pid_max\n");
- return fret;
+ goto out;
}
/*
- * Create 397 processes. This leaves room for do_clone() (398) and
- * one more 399. So creating another process needs to fail.
+ * Create (outer_limit - 4) processes. This leaves room for
+ * do_clone() and one more. So creating another process needs
+ * to fail.
*/
- for (nr_procs = 0; nr_procs < 396; nr_procs++) {
+ for (nr_procs = 0; nr_procs < outer_limit - 4; nr_procs++) {
pid = fork();
if (pid < 0)
goto reap;
@@ -220,20 +289,26 @@ reap:
for (int i = 0; i < nr_procs; i++)
wait_for_pid(pids[i]);
+out:
+ free(pids);
return fret;
}
static int pid_max_nested_limit_inner(void *data)
{
- int fret = -1, nr_procs = 400;
+ int fret = -1, nr_procs = 0;
int fd, ret;
pid_t pid;
- pid_t pids[1000];
+ pid_t *pids;
+
+ pids = malloc(inner_limit * sizeof(pid_t));
+ if (!pids)
+ return -1;
ret = mount("", "/", NULL, MS_PRIVATE | MS_REC, 0);
if (ret) {
fprintf(stderr, "%m - Failed to make rootfs private mount\n");
- return fret;
+ goto out;
}
umount2("/proc", MNT_DETACH);
@@ -241,23 +316,23 @@ static int pid_max_nested_limit_inner(void *data)
ret = mount("proc", "/proc", "proc", 0, NULL);
if (ret) {
fprintf(stderr, "%m - Failed to mount proc\n");
- return fret;
+ goto out;
}
fd = open("/proc/sys/kernel/pid_max", O_RDWR | O_CLOEXEC | O_NOCTTY);
if (fd < 0) {
fprintf(stderr, "%m - Failed to open pid_max\n");
- return fret;
+ goto out;
}
- ret = write(fd, "500", sizeof("500") - 1);
+ ret = write_int_to_fd(fd, inner_limit);
close(fd);
if (ret < 0) {
fprintf(stderr, "%m - Failed to write pid_max\n");
- return fret;
+ goto out;
}
- for (nr_procs = 0; nr_procs < 500; nr_procs++) {
+ for (nr_procs = 0; nr_procs < inner_limit; nr_procs++) {
pid = fork();
if (pid < 0)
break;
@@ -268,7 +343,7 @@ static int pid_max_nested_limit_inner(void *data)
pids[nr_procs] = pid;
}
- if (nr_procs >= 400) {
+ if (nr_procs >= outer_limit) {
fprintf(stderr, "Managed to create processes beyond the configured outer limit\n");
goto reap;
}
@@ -279,6 +354,8 @@ reap:
for (int i = 0; i < nr_procs; i++)
wait_for_pid(pids[i]);
+out:
+ free(pids);
return fret;
}
@@ -307,7 +384,7 @@ static int pid_max_nested_limit_outer(void *data)
return -1;
}
- ret = write(fd, "400", sizeof("400") - 1);
+ ret = write_int_to_fd(fd, outer_limit);
close(fd);
if (ret < 0) {
fprintf(stderr, "%m - Failed to write pid_max\n");
@@ -328,17 +405,32 @@ static int pid_max_nested_limit_outer(void *data)
return 0;
}
-TEST(pid_max_simple)
+FIXTURE(pid_max) {
+ int dummy;
+};
+
+FIXTURE_SETUP(pid_max)
{
- pid_t pid;
+ int min = pid_min();
+ outer_limit = min + 100;
+ inner_limit = min + 200;
+}
+
+FIXTURE_TEARDOWN(pid_max)
+{
+}
+
+TEST_F(pid_max, simple)
+{
+ pid_t pid;
pid = do_clone(pid_max_cb, NULL, CLONE_NEWPID | CLONE_NEWNS);
ASSERT_GT(pid, 0);
ASSERT_EQ(0, wait_for_pid(pid));
}
-TEST(pid_max_nested_limit)
+TEST_F(pid_max, nested_limit)
{
pid_t pid;
@@ -347,7 +439,7 @@ TEST(pid_max_nested_limit)
ASSERT_EQ(0, wait_for_pid(pid));
}
-TEST(pid_max_nested)
+TEST_F(pid_max, nested)
{
pid_t pid;
diff --git a/tools/testing/selftests/pid_namespace/pidns_init_via_setns.c b/tools/testing/selftests/pid_namespace/pidns_init_via_setns.c
new file mode 100644
index 000000000000..520835ca42ed
--- /dev/null
+++ b/tools/testing/selftests/pid_namespace/pidns_init_via_setns.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+#include "../pidfd/pidfd.h"
+
+/*
+ * Test that a process can become PID 1 (init) in a new PID namespace
+ * created via unshare() and joined via setns().
+ *
+ * Flow:
+ * 1. Parent creates a pipe for synchronization.
+ * 2. Parent forks a child.
+ * 3. Parent calls unshare(CLONE_NEWPID) to create a new PID namespace.
+ * 4. Parent signals the child via the pipe.
+ * 5. Child opens parent's /proc/<ppid>/ns/pid_for_children and calls
+ * setns(fd, CLONE_NEWPID) to join the new namespace.
+ * 6. Child forks a grandchild.
+ * 7. Grandchild verifies getpid() == 1.
+ */
+TEST(pidns_init_via_setns)
+{
+ pid_t child, parent_pid;
+ int pipe_fd[2];
+ char buf;
+
+ if (geteuid())
+ ASSERT_EQ(0, unshare(CLONE_NEWUSER));
+
+ parent_pid = getpid();
+
+ ASSERT_EQ(0, pipe(pipe_fd));
+
+ child = fork();
+ ASSERT_GE(child, 0);
+
+ if (child == 0) {
+ char path[256];
+ int nsfd;
+ pid_t grandchild;
+
+ close(pipe_fd[1]);
+
+ /* Wait for parent to complete unshare */
+ ASSERT_EQ(1, read_nointr(pipe_fd[0], &buf, 1));
+ close(pipe_fd[0]);
+
+ snprintf(path, sizeof(path),
+ "/proc/%d/ns/pid_for_children", parent_pid);
+ nsfd = open(path, O_RDONLY);
+ ASSERT_GE(nsfd, 0);
+
+ ASSERT_EQ(0, setns(nsfd, CLONE_NEWPID));
+ close(nsfd);
+
+ grandchild = fork();
+ ASSERT_GE(grandchild, 0);
+
+ if (grandchild == 0) {
+ /* Should be init (PID 1) in the new namespace */
+ if (getpid() != 1)
+ _exit(1);
+ _exit(0);
+ }
+
+ ASSERT_EQ(0, wait_for_pid(grandchild));
+ _exit(0);
+ }
+
+ close(pipe_fd[0]);
+
+ ASSERT_EQ(0, unshare(CLONE_NEWPID));
+
+ /* Signal child that the new PID namespace is ready */
+ buf = 0;
+ ASSERT_EQ(1, write_nointr(pipe_fd[1], &buf, 1));
+ close(pipe_fd[1]);
+
+ ASSERT_EQ(0, wait_for_pid(child));
+}
+
+/*
+ * Similar to pidns_init_via_setns, but:
+ * 1. Parent enters a new PID namespace right from the start to be able to
+ * later freely use pid 1001 in it.
+ * 2. After forking child, parent also calls unshare(CLONE_NEWUSER)
+ * before unshare(CLONE_NEWPID) so that new old and new pid namespaces have
+ * different user namespace owners.
+ * 3. Child uses clone3() with set_tid={1, 1001} instead of fork() and
+ * grandchild checks that it gets desired pids .
+ *
+ * Flow:
+ * 1. Test process creates a new PID namespace and forks a wrapper
+ * (PID 1 in the outer namespace).
+ * 2. Wrapper forks a child.
+ * 3. Wrapper calls unshare(CLONE_NEWUSER) + unshare(CLONE_NEWPID)
+ * to create an inner PID namespace.
+ * 4. Wrapper signals the child via pipe.
+ * 5. Child opens wrapper's /proc/<pid>/ns/pid_for_children and calls
+ * setns(fd, CLONE_NEWPID) to join the inner namespace.
+ * 6. Child calls clone3() with set_tid={1, 1001}.
+ * 7. Grandchild verifies its NSpid ends with "1001 1".
+ */
+
+pid_t set_tid[] = {1, 1001};
+
+static int pidns_init_via_setns_set_tid_grandchild(struct __test_metadata *_metadata)
+{
+ char *line = NULL;
+ size_t len = 0;
+ int found = 0;
+ FILE *gf;
+
+ gf = fopen("/proc/self/status", "r");
+ ASSERT_NE(gf, NULL);
+
+ while (getline(&line, &len, gf) != -1) {
+ if (strncmp(line, "NSpid:", 6) != 0)
+ continue;
+
+ for (int i = 0; i < 2; i++) {
+ char *last = strrchr(line, '\t');
+ pid_t pid;
+
+ ASSERT_NE(last, NULL);
+ ASSERT_EQ(sscanf(last, "%d", &pid), 1);
+ ASSERT_EQ(pid, set_tid[i]);
+ *last = '\0';
+ }
+
+ found = true;
+ break;
+ }
+
+ free(line);
+ fclose(gf);
+ ASSERT_TRUE(found);
+ return 0;
+}
+
+static int pidns_init_via_setns_set_tid_child(struct __test_metadata *_metadata,
+ pid_t parent_pid, int pipe_fd[2])
+{
+ struct __clone_args args = {
+ .exit_signal = SIGCHLD,
+ .set_tid = ptr_to_u64(set_tid),
+ .set_tid_size = 2,
+ };
+ pid_t grandchild;
+ char path[256];
+ char buf;
+ int nsfd;
+
+ close(pipe_fd[1]);
+
+ ASSERT_EQ(1, read_nointr(pipe_fd[0], &buf, 1));
+ close(pipe_fd[0]);
+
+ snprintf(path, sizeof(path),
+ "/proc/%d/ns/pid_for_children", parent_pid);
+ nsfd = open(path, O_RDONLY);
+ ASSERT_GE(nsfd, 0);
+
+ ASSERT_EQ(0, setns(nsfd, CLONE_NEWPID));
+ close(nsfd);
+
+ grandchild = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(grandchild, 0);
+
+ if (grandchild == 0)
+ _exit(pidns_init_via_setns_set_tid_grandchild(_metadata));
+
+ ASSERT_EQ(0, wait_for_pid(grandchild));
+ return 0;
+}
+
+static int pidns_init_via_setns_set_tid_wrapper(struct __test_metadata *_metadata)
+{
+ int pipe_fd[2];
+ pid_t child, parent_pid;
+ char buf;
+ FILE *f;
+
+ /*
+ * We are PID 1 inside the new namespace, but /proc is
+ * mounted from the host. Read our host-visible PID so
+ * the child can reach our pid_for_children via /proc.
+ */
+ f = fopen("/proc/self/stat", "r");
+ ASSERT_NE(f, NULL);
+ ASSERT_EQ(fscanf(f, "%d", &parent_pid), 1);
+ ASSERT_EQ(0, pipe(pipe_fd));
+
+ child = fork();
+ ASSERT_GE(child, 0);
+
+ if (child == 0)
+ _exit(pidns_init_via_setns_set_tid_child(_metadata, parent_pid, pipe_fd));
+
+ close(pipe_fd[0]);
+
+ ASSERT_EQ(0, unshare(CLONE_NEWUSER));
+ ASSERT_EQ(0, unshare(CLONE_NEWPID));
+
+ buf = 0;
+ ASSERT_EQ(1, write_nointr(pipe_fd[1], &buf, 1));
+ close(pipe_fd[1]);
+
+ ASSERT_EQ(0, wait_for_pid(child));
+
+ fclose(f);
+ return 0;
+}
+
+TEST(pidns_init_via_setns_set_tid)
+{
+ pid_t wrapper;
+
+ if (geteuid())
+ SKIP(return, "This test needs root to run!");
+
+ ASSERT_EQ(0, unshare(CLONE_NEWPID));
+
+ wrapper = fork();
+ ASSERT_GE(wrapper, 0);
+
+ if (wrapper == 0)
+ _exit(pidns_init_via_setns_set_tid_wrapper(_metadata));
+
+ ASSERT_EQ(0, wait_for_pid(wrapper));
+}
+
+TEST_HARNESS_MAIN