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

rename_attack_test.c (3669B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * Author: Aleksa Sarai <cyphar@cyphar.com>
      4 * Copyright (C) 2018-2019 SUSE LLC.
      5 */
      6
      7#define _GNU_SOURCE
      8#include <errno.h>
      9#include <fcntl.h>
     10#include <sched.h>
     11#include <sys/stat.h>
     12#include <sys/types.h>
     13#include <sys/mount.h>
     14#include <sys/mman.h>
     15#include <sys/prctl.h>
     16#include <signal.h>
     17#include <stdio.h>
     18#include <stdlib.h>
     19#include <stdbool.h>
     20#include <string.h>
     21#include <syscall.h>
     22#include <limits.h>
     23#include <unistd.h>
     24
     25#include "../kselftest.h"
     26#include "helpers.h"
     27
     28/* Construct a test directory with the following structure:
     29 *
     30 * root/
     31 * |-- a/
     32 * |   `-- c/
     33 * `-- b/
     34 */
     35int setup_testdir(void)
     36{
     37	int dfd;
     38	char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX";
     39
     40	/* Make the top-level directory. */
     41	if (!mkdtemp(dirname))
     42		ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n");
     43	dfd = open(dirname, O_PATH | O_DIRECTORY);
     44	if (dfd < 0)
     45		ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
     46
     47	E_mkdirat(dfd, "a", 0755);
     48	E_mkdirat(dfd, "b", 0755);
     49	E_mkdirat(dfd, "a/c", 0755);
     50
     51	return dfd;
     52}
     53
     54/* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */
     55pid_t spawn_attack(int dirfd, char *a, char *b)
     56{
     57	pid_t child = fork();
     58	if (child != 0)
     59		return child;
     60
     61	/* If the parent (the test process) dies, kill ourselves too. */
     62	E_prctl(PR_SET_PDEATHSIG, SIGKILL);
     63
     64	/* Swap @a and @b. */
     65	for (;;)
     66		renameat2(dirfd, a, dirfd, b, RENAME_EXCHANGE);
     67	exit(1);
     68}
     69
     70#define NUM_RENAME_TESTS 2
     71#define ROUNDS 400000
     72
     73const char *flagname(int resolve)
     74{
     75	switch (resolve) {
     76	case RESOLVE_IN_ROOT:
     77		return "RESOLVE_IN_ROOT";
     78	case RESOLVE_BENEATH:
     79		return "RESOLVE_BENEATH";
     80	}
     81	return "(unknown)";
     82}
     83
     84void test_rename_attack(int resolve)
     85{
     86	int dfd, afd;
     87	pid_t child;
     88	void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
     89	int escapes = 0, other_errs = 0, exdevs = 0, eagains = 0, successes = 0;
     90
     91	struct open_how how = {
     92		.flags = O_PATH,
     93		.resolve = resolve,
     94	};
     95
     96	if (!openat2_supported) {
     97		how.resolve = 0;
     98		ksft_print_msg("openat2(2) unsupported -- using openat(2) instead\n");
     99	}
    100
    101	dfd = setup_testdir();
    102	afd = openat(dfd, "a", O_PATH);
    103	if (afd < 0)
    104		ksft_exit_fail_msg("test_rename_attack: failed to open 'a'\n");
    105
    106	child = spawn_attack(dfd, "a/c", "b");
    107
    108	for (int i = 0; i < ROUNDS; i++) {
    109		int fd;
    110		char *victim_path = "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../..";
    111
    112		if (openat2_supported)
    113			fd = sys_openat2(afd, victim_path, &how);
    114		else
    115			fd = sys_openat(afd, victim_path, &how);
    116
    117		if (fd < 0) {
    118			if (fd == -EAGAIN)
    119				eagains++;
    120			else if (fd == -EXDEV)
    121				exdevs++;
    122			else if (fd == -ENOENT)
    123				escapes++; /* escaped outside and got ENOENT... */
    124			else
    125				other_errs++; /* unexpected error */
    126		} else {
    127			if (fdequal(fd, afd, NULL))
    128				successes++;
    129			else
    130				escapes++; /* we got an unexpected fd */
    131		}
    132		close(fd);
    133	}
    134
    135	if (escapes > 0)
    136		resultfn = ksft_test_result_fail;
    137	ksft_print_msg("non-escapes: EAGAIN=%d EXDEV=%d E<other>=%d success=%d\n",
    138		       eagains, exdevs, other_errs, successes);
    139	resultfn("rename attack with %s (%d runs, got %d escapes)\n",
    140		 flagname(resolve), ROUNDS, escapes);
    141
    142	/* Should be killed anyway, but might as well make sure. */
    143	E_kill(child, SIGKILL);
    144}
    145
    146#define NUM_TESTS NUM_RENAME_TESTS
    147
    148int main(int argc, char **argv)
    149{
    150	ksft_print_header();
    151	ksft_set_plan(NUM_TESTS);
    152
    153	test_rename_attack(RESOLVE_BENEATH);
    154	test_rename_attack(RESOLVE_IN_ROOT);
    155
    156	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
    157		ksft_exit_fail();
    158	else
    159		ksft_exit_pass();
    160}