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