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