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