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
alloc_noexit(unsigned long nr_pages,int pipefd)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 */
run_negative_tests(int pidfd)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
child_main(int pipefd[],size_t size)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
main(void)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