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