1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright 2022 Google LLC 4 */ 5 #define _GNU_SOURCE 6 #include <errno.h> 7 #include <stdbool.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <sys/syscall.h> 11 #include <sys/wait.h> 12 #include <unistd.h> 13 #include <asm-generic/unistd.h> 14 #include "vm_util.h" 15 #include "../kselftest.h" 16 17 #define MB(x) (x << 20) 18 #define MAX_SIZE_MB 1024 19 20 static int alloc_noexit(unsigned long nr_pages, int pipefd) 21 { 22 int ppid = getppid(); 23 int timeout = 10; /* 10sec timeout to get killed */ 24 unsigned long i; 25 char *buf; 26 27 buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE, 28 MAP_PRIVATE | MAP_ANON, 0, 0); 29 if (buf == MAP_FAILED) 30 ksft_exit_fail_msg("mmap failed, halting the test: %s\n", strerror(errno)); 31 32 for (i = 0; i < nr_pages; i++) 33 *((unsigned long *)(buf + (i * psize()))) = i; 34 35 /* Signal the parent that the child is ready */ 36 if (write(pipefd, "", 1) < 0) 37 ksft_exit_fail_msg("write: %s\n", strerror(errno)); 38 39 /* Wait to be killed (when reparenting happens) */ 40 while (getppid() == ppid && timeout > 0) { 41 sleep(1); 42 timeout--; 43 } 44 45 munmap(buf, nr_pages * psize()); 46 47 return (timeout > 0) ? KSFT_PASS : KSFT_FAIL; 48 } 49 50 /* The process_mrelease calls in this test are expected to fail */ 51 static void run_negative_tests(int pidfd) 52 { 53 /* Test invalid flags. Expect to fail with EINVAL error code. */ 54 if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) || 55 errno != EINVAL) { 56 ksft_exit_fail_msg("process_mrelease with wrong flags: %s\n", strerror(errno)); 57 } 58 /* 59 * Test reaping while process is alive with no pending SIGKILL. 60 * Expect to fail with EINVAL error code. 61 */ 62 if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL) 63 ksft_exit_fail_msg("process_mrelease on a live process: %s\n", strerror(errno)); 64 } 65 66 static int child_main(int pipefd[], size_t size) 67 { 68 int res; 69 70 /* Allocate and fault-in memory and wait to be killed */ 71 close(pipefd[0]); 72 res = alloc_noexit(MB(size) / psize(), pipefd[1]); 73 close(pipefd[1]); 74 return res; 75 } 76 77 int main(void) 78 { 79 int pipefd[2], pidfd; 80 bool success, retry; 81 size_t size; 82 pid_t pid; 83 char byte; 84 int res; 85 86 ksft_print_header(); 87 ksft_set_plan(1); 88 89 /* Test a wrong pidfd */ 90 if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) { 91 if (errno == ENOSYS) { 92 ksft_test_result_skip("process_mrelease not implemented\n"); 93 ksft_finished(); 94 } else { 95 ksft_exit_fail_msg("process_mrelease with wrong pidfd: %s", 96 strerror(errno)); 97 } 98 } 99 100 /* Start the test with 1MB child memory allocation */ 101 size = 1; 102 retry: 103 /* 104 * Pipe for the child to signal when it's done allocating 105 * memory 106 */ 107 if (pipe(pipefd)) 108 ksft_exit_fail_msg("pipe: %s\n", strerror(errno)); 109 110 pid = fork(); 111 if (pid < 0) { 112 close(pipefd[0]); 113 close(pipefd[1]); 114 ksft_exit_fail_msg("fork: %s\n", strerror(errno)); 115 } 116 117 if (pid == 0) { 118 /* Child main routine */ 119 res = child_main(pipefd, size); 120 exit(res); 121 } 122 123 /* 124 * Parent main routine: 125 * Wait for the child to finish allocations, then kill and reap 126 */ 127 close(pipefd[1]); 128 /* Block until the child is ready */ 129 res = read(pipefd[0], &byte, 1); 130 close(pipefd[0]); 131 if (res < 0) { 132 if (!kill(pid, SIGKILL)) 133 waitpid(pid, NULL, 0); 134 ksft_exit_fail_msg("read: %s\n", strerror(errno)); 135 } 136 137 pidfd = syscall(__NR_pidfd_open, pid, 0); 138 if (pidfd < 0) { 139 if (!kill(pid, SIGKILL)) 140 waitpid(pid, NULL, 0); 141 ksft_exit_fail_msg("pidfd_open: %s\n", strerror(errno)); 142 } 143 144 /* Run negative tests which require a live child */ 145 run_negative_tests(pidfd); 146 147 if (kill(pid, SIGKILL)) 148 ksft_exit_fail_msg("kill: %s\n", strerror(errno)); 149 150 success = (syscall(__NR_process_mrelease, pidfd, 0) == 0); 151 if (!success) { 152 /* 153 * If we failed to reap because the child exited too soon, 154 * before we could call process_mrelease. Double child's memory 155 * which causes it to spend more time on cleanup and increases 156 * our chances of reaping its memory before it exits. 157 * Retry until we succeed or reach MAX_SIZE_MB. 158 */ 159 if (errno == ESRCH) { 160 retry = (size <= MAX_SIZE_MB); 161 } else { 162 waitpid(pid, NULL, 0); 163 ksft_exit_fail_msg("process_mrelease: %s\n", strerror(errno)); 164 } 165 } 166 167 /* Cleanup to prevent zombies */ 168 if (waitpid(pid, NULL, 0) < 0) 169 ksft_exit_fail_msg("waitpid: %s\n", strerror(errno)); 170 171 close(pidfd); 172 173 if (!success) { 174 if (retry) { 175 size *= 2; 176 goto retry; 177 } 178 ksft_exit_fail_msg("All process_mrelease attempts failed!\n"); 179 } 180 181 ksft_test_result_pass("Success reaping a child with %zuMB of memory allocations\n", 182 size); 183 ksft_finished(); 184 } 185