cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

mrelease_test.c (4438B)


      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}