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