1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Author: Aleksa Sarai <cyphar@cyphar.com>
4 * Copyright (C) 2018-2019 SUSE LLC.
12 #include <sys/types.h>
13 #include <sys/mount.h>
15 #include <sys/prctl.h>
25 #include "../kselftest.h"
28 /* Construct a test directory with the following structure:
35 int setup_testdir(void)
38 char dirname
[] = "/tmp/ksft-openat2-rename-attack.XXXXXX";
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
);
45 ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
47 E_mkdirat(dfd
, "a", 0755);
48 E_mkdirat(dfd
, "b", 0755);
49 E_mkdirat(dfd
, "a/c", 0755);
54 /* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */
55 pid_t
spawn_attack(int dirfd
, char *a
, char *b
)
61 /* If the parent (the test process) dies, kill ourselves too. */
62 E_prctl(PR_SET_PDEATHSIG
, SIGKILL
);
66 renameat2(dirfd
, a
, dirfd
, b
, RENAME_EXCHANGE
);
70 #define NUM_RENAME_TESTS 2
73 const char *flagname(int resolve
)
77 return "RESOLVE_IN_ROOT";
79 return "RESOLVE_BENEATH";
84 void test_rename_attack(int resolve
)
88 void (*resultfn
)(const char *msg
, ...) = ksft_test_result_pass
;
89 int escapes
= 0, other_errs
= 0, exdevs
= 0, eagains
= 0, successes
= 0;
91 struct open_how how
= {
96 if (!openat2_supported
) {
98 ksft_print_msg("openat2(2) unsupported -- using openat(2) instead\n");
101 dfd
= setup_testdir();
102 afd
= openat(dfd
, "a", O_PATH
);
104 ksft_exit_fail_msg("test_rename_attack: failed to open 'a'\n");
106 child
= spawn_attack(dfd
, "a/c", "b");
108 for (int i
= 0; i
< ROUNDS
; i
++) {
110 char *victim_path
= "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../..";
112 if (openat2_supported
)
113 fd
= sys_openat2(afd
, victim_path
, &how
);
115 fd
= sys_openat(afd
, victim_path
, &how
);
120 else if (fd
== -EXDEV
)
122 else if (fd
== -ENOENT
)
123 escapes
++; /* escaped outside and got ENOENT... */
125 other_errs
++; /* unexpected error */
127 if (fdequal(fd
, afd
, NULL
))
130 escapes
++; /* we got an unexpected fd */
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
);
142 /* Should be killed anyway, but might as well make sure. */
143 E_kill(child
, SIGKILL
);
146 #define NUM_TESTS NUM_RENAME_TESTS
148 int main(int argc
, char **argv
)
151 ksft_set_plan(NUM_TESTS
);
153 test_rename_attack(RESOLVE_BENEATH
);
154 test_rename_attack(RESOLVE_IN_ROOT
);
156 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)