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