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