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 
27 static 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 */
62 static 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 
83 static 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 
94 int 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;
112 retry:
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 }
206