| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Based on Christian Brauner's clone3() example. | |
| * These tests are assuming to be running in the host's | |
| * PID namespace. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <linux/types.h> | |
| #include <linux/sched.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdbool.h> | |
| #include <sys/syscall.h> | |
| #include <sys/types.h> | |
| #include <sys/un.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include <sched.h> | |
| #include "kselftest.h" | |
| #include "clone3_selftests.h" | |
| #define MAX_PID_NS_LEVEL 32 | |
| static int pipe_1[2]; | |
| static int pipe_2[2]; | |
| static void child_exit(int ret) | |
| { | |
| fflush(stdout); | |
| fflush(stderr); | |
| _exit(ret); | |
| } | |
| static int call_clone3_set_tid(pid_t *set_tid, | |
| size_t set_tid_size, | |
| int flags, | |
| int expected_pid, | |
| bool wait_for_it) | |
| { | |
| int status; | |
| pid_t pid = -1; | |
| struct __clone_args args = { | |
| .flags = flags, | |
| .exit_signal = SIGCHLD, | |
| .set_tid = ptr_to_u64(set_tid), | |
| .set_tid_size = set_tid_size, | |
| }; | |
| pid = sys_clone3(&args, sizeof(args)); | |
| if (pid < 0) { | |
| ksft_print_msg("%s - Failed to create new process\n", | |
| strerror(errno)); | |
| return -errno; | |
| } | |
| if (pid == 0) { | |
| int ret; | |
| char tmp = 0; | |
| int exit_code = EXIT_SUCCESS; | |
| ksft_print_msg("I am the child, my PID is %d (expected %d)\n", | |
| getpid(), set_tid[0]); | |
| if (wait_for_it) { | |
| ksft_print_msg("[%d] Child is ready and waiting\n", | |
| getpid()); | |
| /* Signal the parent that the child is ready */ | |
| close(pipe_1[0]); | |
| ret = write(pipe_1[1], &tmp, 1); | |
| if (ret != 1) { | |
| ksft_print_msg( | |
| "Writing to pipe returned %d", ret); | |
| exit_code = EXIT_FAILURE; | |
| } | |
| close(pipe_1[1]); | |
| close(pipe_2[1]); | |
| ret = read(pipe_2[0], &tmp, 1); | |
| if (ret != 1) { | |
| ksft_print_msg( | |
| "Reading from pipe returned %d", ret); | |
| exit_code = EXIT_FAILURE; | |
| } | |
| close(pipe_2[0]); | |
| } | |
| if (set_tid[0] != getpid()) | |
| child_exit(EXIT_FAILURE); | |
| child_exit(exit_code); | |
| } | |
| if (expected_pid == 0 || expected_pid == pid) { | |
| ksft_print_msg("I am the parent (%d). My child's pid is %d\n", | |
| getpid(), pid); | |
| } else { | |
| ksft_print_msg( | |
| "Expected child pid %d does not match actual pid %d\n", | |
| expected_pid, pid); | |
| return -1; | |
| } | |
| if (waitpid(pid, &status, 0) < 0) { | |
| ksft_print_msg("Child returned %s\n", strerror(errno)); | |
| return -errno; | |
| } | |
| if (!WIFEXITED(status)) | |
| return -1; | |
| return WEXITSTATUS(status); | |
| } | |
| static void test_clone3_set_tid(const char *desc, | |
| pid_t *set_tid, | |
| size_t set_tid_size, | |
| int flags, | |
| int expected, | |
| int expected_pid, | |
| bool wait_for_it) | |
| { | |
| int ret; | |
| ksft_print_msg( | |
| "[%d] Trying clone3() with CLONE_SET_TID to %d and 0x%x\n", | |
| getpid(), set_tid[0], flags); | |
| ret = call_clone3_set_tid(set_tid, set_tid_size, flags, expected_pid, | |
| wait_for_it); | |
| ksft_print_msg( | |
| "[%d] clone3() with CLONE_SET_TID %d says: %d - expected %d\n", | |
| getpid(), set_tid[0], ret, expected); | |
| ksft_test_result(ret == expected, "%s with %zu TIDs and flags 0x%x\n", | |
| desc, set_tid_size, flags); | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| FILE *f; | |
| char buf; | |
| char *line = NULL; | |
| int status; | |
| int ret = -1; | |
| size_t len = 0; | |
| int pid_max = 0; | |
| uid_t uid = getuid(); | |
| char proc_path[100] = {0}; | |
| pid_t pid, ns1, ns2, ns3, ns_pid; | |
| pid_t set_tid[MAX_PID_NS_LEVEL * 2]; | |
| ksft_print_header(); | |
| ksft_set_plan(29); | |
| test_clone3_supported(); | |
| if (pipe(pipe_1) < 0 || pipe(pipe_2) < 0) | |
| ksft_exit_fail_msg("pipe() failed\n"); | |
| f = fopen("/proc/sys/kernel/pid_max", "r"); | |
| if (f == NULL) | |
| ksft_exit_fail_msg( | |
| "%s - Could not open /proc/sys/kernel/pid_max\n", | |
| strerror(errno)); | |
| fscanf(f, "%d", &pid_max); | |
| fclose(f); | |
| ksft_print_msg("/proc/sys/kernel/pid_max %d\n", pid_max); | |
| /* Try invalid settings */ | |
| memset(&set_tid, 0, sizeof(set_tid)); | |
| test_clone3_set_tid("invalid size, 0 TID", | |
| set_tid, MAX_PID_NS_LEVEL + 1, 0, -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, 0 TID", | |
| set_tid, MAX_PID_NS_LEVEL * 2, 0, -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, 0 TID", | |
| set_tid, MAX_PID_NS_LEVEL * 2 + 1, 0, | |
| -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, 0 TID", | |
| set_tid, MAX_PID_NS_LEVEL * 42, 0, -EINVAL, 0, 0); | |
| /* | |
| * This can actually work if this test running in a MAX_PID_NS_LEVEL - 1 | |
| * nested PID namespace. | |
| */ | |
| test_clone3_set_tid("invalid size, 0 TID", | |
| set_tid, MAX_PID_NS_LEVEL - 1, 0, -EINVAL, 0, 0); | |
| memset(&set_tid, 0xff, sizeof(set_tid)); | |
| test_clone3_set_tid("invalid size, TID all 1s", | |
| set_tid, MAX_PID_NS_LEVEL + 1, 0, -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, TID all 1s", | |
| set_tid, MAX_PID_NS_LEVEL * 2, 0, -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, TID all 1s", | |
| set_tid, MAX_PID_NS_LEVEL * 2 + 1, 0, | |
| -EINVAL, 0, 0); | |
| test_clone3_set_tid("invalid size, TID all 1s", | |
| set_tid, MAX_PID_NS_LEVEL * 42, 0, -EINVAL, 0, 0); | |
| /* | |
| * This can actually work if this test running in a MAX_PID_NS_LEVEL - 1 | |
| * nested PID namespace. | |
| */ | |
| test_clone3_set_tid("invalid size, TID all 1s", | |
| set_tid, MAX_PID_NS_LEVEL - 1, 0, -EINVAL, 0, 0); | |
| memset(&set_tid, 0, sizeof(set_tid)); | |
| /* Try with an invalid PID */ | |
| set_tid[0] = 0; | |
| test_clone3_set_tid("valid size, 0 TID", | |
| set_tid, 1, 0, -EINVAL, 0, 0); | |
| set_tid[0] = -1; | |
| test_clone3_set_tid("valid size, -1 TID", | |
| set_tid, 1, 0, -EINVAL, 0, 0); | |
| /* Claim that the set_tid array actually contains 2 elements. */ | |
| test_clone3_set_tid("2 TIDs, -1 and 0", | |
| set_tid, 2, 0, -EINVAL, 0, 0); | |
| /* Try it in a new PID namespace */ | |
| if (uid == 0) | |
| test_clone3_set_tid("valid size, -1 TID", | |
| set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0); | |
| else | |
| ksft_test_result_skip("Clone3() with set_tid requires root\n"); | |
| /* Try with a valid PID (1) this should return -EEXIST. */ | |
| set_tid[0] = 1; | |
| if (uid == 0) | |
| test_clone3_set_tid("duplicate PID 1", | |
| set_tid, 1, 0, -EEXIST, 0, 0); | |
| else | |
| ksft_test_result_skip("Clone3() with set_tid requires root\n"); | |
| /* Try it in a new PID namespace */ | |
| if (uid == 0) | |
| test_clone3_set_tid("duplicate PID 1", | |
| set_tid, 1, CLONE_NEWPID, 0, 0, 0); | |
| else | |
| ksft_test_result_skip("Clone3() with set_tid requires root\n"); | |
| /* pid_max should fail everywhere */ | |
| set_tid[0] = pid_max; | |
| test_clone3_set_tid("set TID to maximum", | |
| set_tid, 1, 0, -EINVAL, 0, 0); | |
| if (uid == 0) | |
| test_clone3_set_tid("set TID to maximum", | |
| set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0); | |
| else | |
| ksft_test_result_skip("Clone3() with set_tid requires root\n"); | |
| if (uid != 0) { | |
| /* | |
| * All remaining tests require root. Tell the framework | |
| * that all those tests are skipped as non-root. | |
| */ | |
| ksft_cnt.ksft_xskip += ksft_plan - ksft_test_num(); | |
| goto out; | |
| } | |
| /* Find the current active PID */ | |
| pid = fork(); | |
| if (pid == 0) { | |
| ksft_print_msg("Child has PID %d\n", getpid()); | |
| child_exit(EXIT_SUCCESS); | |
| } | |
| if (waitpid(pid, &status, 0) < 0) | |
| ksft_exit_fail_msg("Waiting for child %d failed", pid); | |
| /* After the child has finished, its PID should be free. */ | |
| set_tid[0] = pid; | |
| test_clone3_set_tid("reallocate child TID", | |
| set_tid, 1, 0, 0, 0, 0); | |
| /* This should fail as there is no PID 1 in that namespace */ | |
| test_clone3_set_tid("duplicate child TID", | |
| set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0); | |
| /* | |
| * Creating a process with PID 1 in the newly created most nested | |
| * PID namespace and PID 'pid' in the parent PID namespace. This | |
| * needs to work. | |
| */ | |
| set_tid[0] = 1; | |
| set_tid[1] = pid; | |
| test_clone3_set_tid("create PID 1 in new NS", | |
| set_tid, 2, CLONE_NEWPID, 0, pid, 0); | |
| ksft_print_msg("unshare PID namespace\n"); | |
| if (unshare(CLONE_NEWPID) == -1) | |
| ksft_exit_fail_msg("unshare(CLONE_NEWPID) failed: %s\n", | |
| strerror(errno)); | |
| set_tid[0] = pid; | |
| /* This should fail as there is no PID 1 in that namespace */ | |
| test_clone3_set_tid("duplicate PID 1", | |
| set_tid, 1, 0, -EINVAL, 0, 0); | |
| /* Let's create a PID 1 */ | |
| ns_pid = fork(); | |
| if (ns_pid == 0) { | |
| /* | |
| * This and the next test cases check that all pid-s are | |
| * released on error paths. | |
| */ | |
| set_tid[0] = 43; | |
| set_tid[1] = -1; | |
| test_clone3_set_tid("check leak on invalid TID -1", | |
| set_tid, 2, 0, -EINVAL, 0, 0); | |
| set_tid[0] = 43; | |
| set_tid[1] = pid; | |
| test_clone3_set_tid("check leak on invalid specific TID", | |
| set_tid, 2, 0, 0, 43, 0); | |
| ksft_print_msg("Child in PID namespace has PID %d\n", getpid()); | |
| set_tid[0] = 2; | |
| test_clone3_set_tid("create PID 2 in child NS", | |
| set_tid, 1, 0, 0, 2, 0); | |
| set_tid[0] = 1; | |
| set_tid[1] = -1; | |
| set_tid[2] = pid; | |
| /* This should fail as there is invalid PID at level '1'. */ | |
| test_clone3_set_tid("fail due to invalid TID at level 1", | |
| set_tid, 3, CLONE_NEWPID, -EINVAL, 0, 0); | |
| set_tid[0] = 1; | |
| set_tid[1] = 42; | |
| set_tid[2] = pid; | |
| /* | |
| * This should fail as there are not enough active PID | |
| * namespaces. Again assuming this is running in the host's | |
| * PID namespace. Not yet nested. | |
| */ | |
| test_clone3_set_tid("fail due to too few active PID NSs", | |
| set_tid, 4, CLONE_NEWPID, -EINVAL, 0, 0); | |
| /* | |
| * This should work and from the parent we should see | |
| * something like 'NSpid: pid 42 1'. | |
| */ | |
| test_clone3_set_tid("verify that we have 3 PID NSs", | |
| set_tid, 3, CLONE_NEWPID, 0, 42, true); | |
| child_exit(ksft_cnt.ksft_fail); | |
| } | |
| close(pipe_1[1]); | |
| close(pipe_2[0]); | |
| while (read(pipe_1[0], &buf, 1) > 0) { | |
| ksft_print_msg("[%d] Child is ready and waiting\n", getpid()); | |
| break; | |
| } | |
| snprintf(proc_path, sizeof(proc_path), "/proc/%d/status", pid); | |
| f = fopen(proc_path, "r"); | |
| if (f == NULL) | |
| ksft_exit_fail_msg( | |
| "%s - Could not open %s\n", | |
| strerror(errno), proc_path); | |
| while (getline(&line, &len, f) != -1) { | |
| if (strstr(line, "NSpid")) { | |
| int i; | |
| /* Verify that all generated PIDs are as expected. */ | |
| i = sscanf(line, "NSpid:\t%d\t%d\t%d", | |
| &ns3, &ns2, &ns1); | |
| if (i != 3) { | |
| ksft_print_msg( | |
| "Unexpected 'NSPid:' entry: %s", | |
| line); | |
| ns1 = ns2 = ns3 = 0; | |
| } | |
| break; | |
| } | |
| } | |
| fclose(f); | |
| free(line); | |
| close(pipe_2[0]); | |
| /* Tell the clone3()'d child to finish. */ | |
| write(pipe_2[1], &buf, 1); | |
| close(pipe_2[1]); | |
| if (waitpid(ns_pid, &status, 0) < 0) { | |
| ksft_print_msg("Child returned %s\n", strerror(errno)); | |
| ret = -errno; | |
| goto out; | |
| } | |
| if (!WIFEXITED(status)) | |
| ksft_test_result_fail("Child error\n"); | |
| ksft_cnt.ksft_pass += 6 - (ksft_cnt.ksft_fail - WEXITSTATUS(status)); | |
| ksft_cnt.ksft_fail = WEXITSTATUS(status); | |
| ksft_print_msg("Expecting PIDs %d, 42, 1\n", pid); | |
| ksft_print_msg("Have PIDs in namespaces: %d, %d, %d\n", ns3, ns2, ns1); | |
| ksft_test_result(ns3 == pid && ns2 == 42 && ns1 == 1, | |
| "PIDs in all namespaces as expected\n"); | |
| out: | |
| ret = 0; | |
| if (ret) | |
| ksft_exit_fail(); | |
| ksft_exit_pass(); | |
| } |