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