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