brintos

brintos / linux-shallow public Read only

0
0
Text · 3.6 KiB · 0a77072 Raw
161 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Author: Aleksa Sarai <cyphar@cyphar.com>4 * Copyright (C) 2018-2019 SUSE LLC.5 */6 7#define _GNU_SOURCE8#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 271#define ROUNDS 40000072 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		else115			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			else125				other_errs++; /* unexpected error */126		} else {127			if (fdequal(fd, afd, NULL))128				successes++;129			else130				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_TESTS147 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	else159		ksft_exit_pass();160}161