// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2022 Google LLC */ #define _GNU_SOURCE #include <errno.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #include <asm-generic/unistd.h> #include "vm_util.h" #include "../kselftest.h" #define MB(x) (x << 20) #define MAX_SIZE_MB 1024 static int alloc_noexit(unsigned long nr_pages, int pipefd) { int ppid = getppid(); int timeout = 10; /* 10sec timeout to get killed */ unsigned long i; char *buf; buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0); if (buf == MAP_FAILED) { perror("mmap failed, halting the test"); return KSFT_FAIL; } for (i = 0; i < nr_pages; i++) *((unsigned long *)(buf + (i * psize()))) = i; /* Signal the parent that the child is ready */ if (write(pipefd, "", 1) < 0) { perror("write"); return KSFT_FAIL; } /* Wait to be killed (when reparenting happens) */ while (getppid() == ppid && timeout > 0) { sleep(1); timeout--; } munmap(buf, nr_pages * psize()); return (timeout > 0) ? KSFT_PASS : KSFT_FAIL; } /* The process_mrelease calls in this test are expected to fail */ static void run_negative_tests(int pidfd) { int res; /* Test invalid flags. Expect to fail with EINVAL error code. */ if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) || errno != EINVAL) { res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); perror("process_mrelease with wrong flags"); exit(res); } /* * Test reaping while process is alive with no pending SIGKILL. * Expect to fail with EINVAL error code. */ if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL) { res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); perror("process_mrelease on a live process"); exit(res); } } static int child_main(int pipefd[], size_t size) { int res; /* Allocate and fault-in memory and wait to be killed */ close(pipefd[0]); res = alloc_noexit(MB(size) / psize(), pipefd[1]); close(pipefd[1]); return res; } int main(void) { int pipefd[2], pidfd; bool success, retry; size_t size; pid_t pid; char byte; int res; /* Test a wrong pidfd */ if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) { res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); perror("process_mrelease with wrong pidfd"); exit(res); } /* Start the test with 1MB child memory allocation */ size = 1; retry: /* * Pipe for the child to signal when it's done allocating * memory */ if (pipe(pipefd)) { perror("pipe"); exit(KSFT_FAIL); } pid = fork(); if (pid < 0) { perror("fork"); close(pipefd[0]); close(pipefd[1]); exit(KSFT_FAIL); } if (pid == 0) { /* Child main routine */ res = child_main(pipefd, size); exit(res); } /* * Parent main routine: * Wait for the child to finish allocations, then kill and reap */ close(pipefd[1]); /* Block until the child is ready */ res = read(pipefd[0], &byte, 1); close(pipefd[0]); if (res < 0) { perror("read"); if (!kill(pid, SIGKILL)) waitpid(pid, NULL, 0); exit(KSFT_FAIL); } pidfd = syscall(__NR_pidfd_open, pid, 0); if (pidfd < 0) { perror("pidfd_open"); if (!kill(pid, SIGKILL)) waitpid(pid, NULL, 0); exit(KSFT_FAIL); } /* Run negative tests which require a live child */ run_negative_tests(pidfd); if (kill(pid, SIGKILL)) { res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); perror("kill"); exit(res); } success = (syscall(__NR_process_mrelease, pidfd, 0) == 0); if (!success) { /* * If we failed to reap because the child exited too soon, * before we could call process_mrelease. Double child's memory * which causes it to spend more time on cleanup and increases * our chances of reaping its memory before it exits. * Retry until we succeed or reach MAX_SIZE_MB. */ if (errno == ESRCH) { retry = (size <= MAX_SIZE_MB); } else { res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL); perror("process_mrelease"); waitpid(pid, NULL, 0); exit(res); } } /* Cleanup to prevent zombies */ if (waitpid(pid, NULL, 0) < 0) { perror("waitpid"); exit(KSFT_FAIL); } close(pidfd); if (!success) { if (retry) { size *= 2; goto retry; } printf("All process_mrelease attempts failed!\n"); exit(KSFT_FAIL); } printf("Success reaping a child with %zuMB of memory allocations\n", size); return KSFT_PASS; }