mrelease_test.c (4438B)
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 28static 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 */ 63static void run_negative_tests(int pidfd) 64{ 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 perror("process_mrelease with wrong flags"); 69 exit(errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); 70 } 71 /* 72 * Test reaping while process is alive with no pending SIGKILL. 73 * Expect to fail with EINVAL error code. 74 */ 75 if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL) { 76 perror("process_mrelease on a live process"); 77 exit(errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); 78 } 79} 80 81static int child_main(int pipefd[], size_t size) 82{ 83 int res; 84 85 /* Allocate and fault-in memory and wait to be killed */ 86 close(pipefd[0]); 87 res = alloc_noexit(MB(size) / PAGE_SIZE, pipefd[1]); 88 close(pipefd[1]); 89 return res; 90} 91 92int main(void) 93{ 94 int pipefd[2], pidfd; 95 bool success, retry; 96 size_t size; 97 pid_t pid; 98 char byte; 99 int res; 100 101 /* Test a wrong pidfd */ 102 if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) { 103 perror("process_mrelease with wrong pidfd"); 104 exit(errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); 105 } 106 107 /* Start the test with 1MB child memory allocation */ 108 size = 1; 109retry: 110 /* 111 * Pipe for the child to signal when it's done allocating 112 * memory 113 */ 114 if (pipe(pipefd)) { 115 perror("pipe"); 116 exit(KSFT_FAIL); 117 } 118 pid = fork(); 119 if (pid < 0) { 120 perror("fork"); 121 close(pipefd[0]); 122 close(pipefd[1]); 123 exit(KSFT_FAIL); 124 } 125 126 if (pid == 0) { 127 /* Child main routine */ 128 res = child_main(pipefd, size); 129 exit(res); 130 } 131 132 /* 133 * Parent main routine: 134 * Wait for the child to finish allocations, then kill and reap 135 */ 136 close(pipefd[1]); 137 /* Block until the child is ready */ 138 res = read(pipefd[0], &byte, 1); 139 close(pipefd[0]); 140 if (res < 0) { 141 perror("read"); 142 if (!kill(pid, SIGKILL)) 143 waitpid(pid, NULL, 0); 144 exit(KSFT_FAIL); 145 } 146 147 pidfd = syscall(__NR_pidfd_open, pid, 0); 148 if (pidfd < 0) { 149 perror("pidfd_open"); 150 if (!kill(pid, SIGKILL)) 151 waitpid(pid, NULL, 0); 152 exit(KSFT_FAIL); 153 } 154 155 /* Run negative tests which require a live child */ 156 run_negative_tests(pidfd); 157 158 if (kill(pid, SIGKILL)) { 159 perror("kill"); 160 exit(errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); 161 } 162 163 success = (syscall(__NR_process_mrelease, pidfd, 0) == 0); 164 if (!success) { 165 /* 166 * If we failed to reap because the child exited too soon, 167 * before we could call process_mrelease. Double child's memory 168 * which causes it to spend more time on cleanup and increases 169 * our chances of reaping its memory before it exits. 170 * Retry until we succeed or reach MAX_SIZE_MB. 171 */ 172 if (errno == ESRCH) { 173 retry = (size <= MAX_SIZE_MB); 174 } else { 175 perror("process_mrelease"); 176 waitpid(pid, NULL, 0); 177 exit(errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); 178 } 179 } 180 181 /* Cleanup to prevent zombies */ 182 if (waitpid(pid, NULL, 0) < 0) { 183 perror("waitpid"); 184 exit(KSFT_FAIL); 185 } 186 close(pidfd); 187 188 if (!success) { 189 if (retry) { 190 size *= 2; 191 goto retry; 192 } 193 printf("All process_mrelease attempts failed!\n"); 194 exit(KSFT_FAIL); 195 } 196 197 printf("Success reaping a child with %zuMB of memory allocations\n", 198 size); 199 return KSFT_PASS; 200}