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