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