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 
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 */
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 
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 
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