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