xref: /OK3568_Linux_fs/kernel/tools/testing/selftests/openat2/resolve_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 <fcntl.h>
9*4882a593Smuzhiyun #include <sched.h>
10*4882a593Smuzhiyun #include <sys/stat.h>
11*4882a593Smuzhiyun #include <sys/types.h>
12*4882a593Smuzhiyun #include <sys/mount.h>
13*4882a593Smuzhiyun #include <stdlib.h>
14*4882a593Smuzhiyun #include <stdbool.h>
15*4882a593Smuzhiyun #include <string.h>
16*4882a593Smuzhiyun 
17*4882a593Smuzhiyun #include "../kselftest.h"
18*4882a593Smuzhiyun #include "helpers.h"
19*4882a593Smuzhiyun 
20*4882a593Smuzhiyun /*
21*4882a593Smuzhiyun  * Construct a test directory with the following structure:
22*4882a593Smuzhiyun  *
23*4882a593Smuzhiyun  * root/
24*4882a593Smuzhiyun  * |-- procexe -> /proc/self/exe
25*4882a593Smuzhiyun  * |-- procroot -> /proc/self/root
26*4882a593Smuzhiyun  * |-- root/
27*4882a593Smuzhiyun  * |-- mnt/ [mountpoint]
28*4882a593Smuzhiyun  * |   |-- self -> ../mnt/
29*4882a593Smuzhiyun  * |   `-- absself -> /mnt/
30*4882a593Smuzhiyun  * |-- etc/
31*4882a593Smuzhiyun  * |   `-- passwd
32*4882a593Smuzhiyun  * |-- creatlink -> /newfile3
33*4882a593Smuzhiyun  * |-- reletc -> etc/
34*4882a593Smuzhiyun  * |-- relsym -> etc/passwd
35*4882a593Smuzhiyun  * |-- absetc -> /etc/
36*4882a593Smuzhiyun  * |-- abssym -> /etc/passwd
37*4882a593Smuzhiyun  * |-- abscheeky -> /cheeky
38*4882a593Smuzhiyun  * `-- cheeky/
39*4882a593Smuzhiyun  *     |-- absself -> /
40*4882a593Smuzhiyun  *     |-- self -> ../../root/
41*4882a593Smuzhiyun  *     |-- garbageself -> /../../root/
42*4882a593Smuzhiyun  *     |-- passwd -> ../cheeky/../cheeky/../etc/../etc/passwd
43*4882a593Smuzhiyun  *     |-- abspasswd -> /../cheeky/../cheeky/../etc/../etc/passwd
44*4882a593Smuzhiyun  *     |-- dotdotlink -> ../../../../../../../../../../../../../../etc/passwd
45*4882a593Smuzhiyun  *     `-- garbagelink -> /../../../../../../../../../../../../../../etc/passwd
46*4882a593Smuzhiyun  */
setup_testdir(void)47*4882a593Smuzhiyun int setup_testdir(void)
48*4882a593Smuzhiyun {
49*4882a593Smuzhiyun 	int dfd, tmpfd;
50*4882a593Smuzhiyun 	char dirname[] = "/tmp/ksft-openat2-testdir.XXXXXX";
51*4882a593Smuzhiyun 
52*4882a593Smuzhiyun 	/* Unshare and make /tmp a new directory. */
53*4882a593Smuzhiyun 	E_unshare(CLONE_NEWNS);
54*4882a593Smuzhiyun 	E_mount("", "/tmp", "", MS_PRIVATE, "");
55*4882a593Smuzhiyun 
56*4882a593Smuzhiyun 	/* Make the top-level directory. */
57*4882a593Smuzhiyun 	if (!mkdtemp(dirname))
58*4882a593Smuzhiyun 		ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n");
59*4882a593Smuzhiyun 	dfd = open(dirname, O_PATH | O_DIRECTORY);
60*4882a593Smuzhiyun 	if (dfd < 0)
61*4882a593Smuzhiyun 		ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun 	/* A sub-directory which is actually used for tests. */
64*4882a593Smuzhiyun 	E_mkdirat(dfd, "root", 0755);
65*4882a593Smuzhiyun 	tmpfd = openat(dfd, "root", O_PATH | O_DIRECTORY);
66*4882a593Smuzhiyun 	if (tmpfd < 0)
67*4882a593Smuzhiyun 		ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
68*4882a593Smuzhiyun 	close(dfd);
69*4882a593Smuzhiyun 	dfd = tmpfd;
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 	E_symlinkat("/proc/self/exe", dfd, "procexe");
72*4882a593Smuzhiyun 	E_symlinkat("/proc/self/root", dfd, "procroot");
73*4882a593Smuzhiyun 	E_mkdirat(dfd, "root", 0755);
74*4882a593Smuzhiyun 
75*4882a593Smuzhiyun 	/* There is no mountat(2), so use chdir. */
76*4882a593Smuzhiyun 	E_mkdirat(dfd, "mnt", 0755);
77*4882a593Smuzhiyun 	E_fchdir(dfd);
78*4882a593Smuzhiyun 	E_mount("tmpfs", "./mnt", "tmpfs", MS_NOSUID | MS_NODEV, "");
79*4882a593Smuzhiyun 	E_symlinkat("../mnt/", dfd, "mnt/self");
80*4882a593Smuzhiyun 	E_symlinkat("/mnt/", dfd, "mnt/absself");
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun 	E_mkdirat(dfd, "etc", 0755);
83*4882a593Smuzhiyun 	E_touchat(dfd, "etc/passwd");
84*4882a593Smuzhiyun 
85*4882a593Smuzhiyun 	E_symlinkat("/newfile3", dfd, "creatlink");
86*4882a593Smuzhiyun 	E_symlinkat("etc/", dfd, "reletc");
87*4882a593Smuzhiyun 	E_symlinkat("etc/passwd", dfd, "relsym");
88*4882a593Smuzhiyun 	E_symlinkat("/etc/", dfd, "absetc");
89*4882a593Smuzhiyun 	E_symlinkat("/etc/passwd", dfd, "abssym");
90*4882a593Smuzhiyun 	E_symlinkat("/cheeky", dfd, "abscheeky");
91*4882a593Smuzhiyun 
92*4882a593Smuzhiyun 	E_mkdirat(dfd, "cheeky", 0755);
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun 	E_symlinkat("/", dfd, "cheeky/absself");
95*4882a593Smuzhiyun 	E_symlinkat("../../root/", dfd, "cheeky/self");
96*4882a593Smuzhiyun 	E_symlinkat("/../../root/", dfd, "cheeky/garbageself");
97*4882a593Smuzhiyun 
98*4882a593Smuzhiyun 	E_symlinkat("../cheeky/../etc/../etc/passwd", dfd, "cheeky/passwd");
99*4882a593Smuzhiyun 	E_symlinkat("/../cheeky/../etc/../etc/passwd", dfd, "cheeky/abspasswd");
100*4882a593Smuzhiyun 
101*4882a593Smuzhiyun 	E_symlinkat("../../../../../../../../../../../../../../etc/passwd",
102*4882a593Smuzhiyun 		    dfd, "cheeky/dotdotlink");
103*4882a593Smuzhiyun 	E_symlinkat("/../../../../../../../../../../../../../../etc/passwd",
104*4882a593Smuzhiyun 		    dfd, "cheeky/garbagelink");
105*4882a593Smuzhiyun 
106*4882a593Smuzhiyun 	return dfd;
107*4882a593Smuzhiyun }
108*4882a593Smuzhiyun 
109*4882a593Smuzhiyun struct basic_test {
110*4882a593Smuzhiyun 	const char *name;
111*4882a593Smuzhiyun 	const char *dir;
112*4882a593Smuzhiyun 	const char *path;
113*4882a593Smuzhiyun 	struct open_how how;
114*4882a593Smuzhiyun 	bool pass;
115*4882a593Smuzhiyun 	union {
116*4882a593Smuzhiyun 		int err;
117*4882a593Smuzhiyun 		const char *path;
118*4882a593Smuzhiyun 	} out;
119*4882a593Smuzhiyun };
120*4882a593Smuzhiyun 
121*4882a593Smuzhiyun #define NUM_OPENAT2_OPATH_TESTS 88
122*4882a593Smuzhiyun 
test_openat2_opath_tests(void)123*4882a593Smuzhiyun void test_openat2_opath_tests(void)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun 	int rootfd, hardcoded_fd;
126*4882a593Smuzhiyun 	char *procselfexe, *hardcoded_fdpath;
127*4882a593Smuzhiyun 
128*4882a593Smuzhiyun 	E_asprintf(&procselfexe, "/proc/%d/exe", getpid());
129*4882a593Smuzhiyun 	rootfd = setup_testdir();
130*4882a593Smuzhiyun 
131*4882a593Smuzhiyun 	hardcoded_fd = open("/dev/null", O_RDONLY);
132*4882a593Smuzhiyun 	E_assert(hardcoded_fd >= 0, "open fd to hardcode");
133*4882a593Smuzhiyun 	E_asprintf(&hardcoded_fdpath, "self/fd/%d", hardcoded_fd);
134*4882a593Smuzhiyun 
135*4882a593Smuzhiyun 	struct basic_test tests[] = {
136*4882a593Smuzhiyun 		/** RESOLVE_BENEATH **/
137*4882a593Smuzhiyun 		/* Attempts to cross dirfd should be blocked. */
138*4882a593Smuzhiyun 		{ .name = "[beneath] jump to /",
139*4882a593Smuzhiyun 		  .path = "/",			.how.resolve = RESOLVE_BENEATH,
140*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
141*4882a593Smuzhiyun 		{ .name = "[beneath] absolute link to $root",
142*4882a593Smuzhiyun 		  .path = "cheeky/absself",	.how.resolve = RESOLVE_BENEATH,
143*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
144*4882a593Smuzhiyun 		{ .name = "[beneath] chained absolute links to $root",
145*4882a593Smuzhiyun 		  .path = "abscheeky/absself",	.how.resolve = RESOLVE_BENEATH,
146*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
147*4882a593Smuzhiyun 		{ .name = "[beneath] jump outside $root",
148*4882a593Smuzhiyun 		  .path = "..",			.how.resolve = RESOLVE_BENEATH,
149*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
150*4882a593Smuzhiyun 		{ .name = "[beneath] temporary jump outside $root",
151*4882a593Smuzhiyun 		  .path = "../root/",		.how.resolve = RESOLVE_BENEATH,
152*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
153*4882a593Smuzhiyun 		{ .name = "[beneath] symlink temporary jump outside $root",
154*4882a593Smuzhiyun 		  .path = "cheeky/self",	.how.resolve = RESOLVE_BENEATH,
155*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
156*4882a593Smuzhiyun 		{ .name = "[beneath] chained symlink temporary jump outside $root",
157*4882a593Smuzhiyun 		  .path = "abscheeky/self",	.how.resolve = RESOLVE_BENEATH,
158*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
159*4882a593Smuzhiyun 		{ .name = "[beneath] garbage links to $root",
160*4882a593Smuzhiyun 		  .path = "cheeky/garbageself",	.how.resolve = RESOLVE_BENEATH,
161*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
162*4882a593Smuzhiyun 		{ .name = "[beneath] chained garbage links to $root",
163*4882a593Smuzhiyun 		  .path = "abscheeky/garbageself", .how.resolve = RESOLVE_BENEATH,
164*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
165*4882a593Smuzhiyun 		/* Only relative paths that stay inside dirfd should work. */
166*4882a593Smuzhiyun 		{ .name = "[beneath] ordinary path to 'root'",
167*4882a593Smuzhiyun 		  .path = "root",		.how.resolve = RESOLVE_BENEATH,
168*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
169*4882a593Smuzhiyun 		{ .name = "[beneath] ordinary path to 'etc'",
170*4882a593Smuzhiyun 		  .path = "etc",		.how.resolve = RESOLVE_BENEATH,
171*4882a593Smuzhiyun 		  .out.path = "etc",		.pass = true },
172*4882a593Smuzhiyun 		{ .name = "[beneath] ordinary path to 'etc/passwd'",
173*4882a593Smuzhiyun 		  .path = "etc/passwd",		.how.resolve = RESOLVE_BENEATH,
174*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
175*4882a593Smuzhiyun 		{ .name = "[beneath] relative symlink inside $root",
176*4882a593Smuzhiyun 		  .path = "relsym",		.how.resolve = RESOLVE_BENEATH,
177*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
178*4882a593Smuzhiyun 		{ .name = "[beneath] chained-'..' relative symlink inside $root",
179*4882a593Smuzhiyun 		  .path = "cheeky/passwd",	.how.resolve = RESOLVE_BENEATH,
180*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
181*4882a593Smuzhiyun 		{ .name = "[beneath] absolute symlink component outside $root",
182*4882a593Smuzhiyun 		  .path = "abscheeky/passwd",	.how.resolve = RESOLVE_BENEATH,
183*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
184*4882a593Smuzhiyun 		{ .name = "[beneath] absolute symlink target outside $root",
185*4882a593Smuzhiyun 		  .path = "abssym",		.how.resolve = RESOLVE_BENEATH,
186*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
187*4882a593Smuzhiyun 		{ .name = "[beneath] absolute path outside $root",
188*4882a593Smuzhiyun 		  .path = "/etc/passwd",	.how.resolve = RESOLVE_BENEATH,
189*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
190*4882a593Smuzhiyun 		{ .name = "[beneath] cheeky absolute path outside $root",
191*4882a593Smuzhiyun 		  .path = "cheeky/abspasswd",	.how.resolve = RESOLVE_BENEATH,
192*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
193*4882a593Smuzhiyun 		{ .name = "[beneath] chained cheeky absolute path outside $root",
194*4882a593Smuzhiyun 		  .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_BENEATH,
195*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
196*4882a593Smuzhiyun 		/* Tricky paths should fail. */
197*4882a593Smuzhiyun 		{ .name = "[beneath] tricky '..'-chained symlink outside $root",
198*4882a593Smuzhiyun 		  .path = "cheeky/dotdotlink",	.how.resolve = RESOLVE_BENEATH,
199*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
200*4882a593Smuzhiyun 		{ .name = "[beneath] tricky absolute + '..'-chained symlink outside $root",
201*4882a593Smuzhiyun 		  .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_BENEATH,
202*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
203*4882a593Smuzhiyun 		{ .name = "[beneath] tricky garbage link outside $root",
204*4882a593Smuzhiyun 		  .path = "cheeky/garbagelink",	.how.resolve = RESOLVE_BENEATH,
205*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
206*4882a593Smuzhiyun 		{ .name = "[beneath] tricky absolute + garbage link outside $root",
207*4882a593Smuzhiyun 		  .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_BENEATH,
208*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
209*4882a593Smuzhiyun 
210*4882a593Smuzhiyun 		/** RESOLVE_IN_ROOT **/
211*4882a593Smuzhiyun 		/* All attempts to cross the dirfd will be scoped-to-root. */
212*4882a593Smuzhiyun 		{ .name = "[in_root] jump to /",
213*4882a593Smuzhiyun 		  .path = "/",			.how.resolve = RESOLVE_IN_ROOT,
214*4882a593Smuzhiyun 		  .out.path = NULL,		.pass = true },
215*4882a593Smuzhiyun 		{ .name = "[in_root] absolute symlink to /root",
216*4882a593Smuzhiyun 		  .path = "cheeky/absself",	.how.resolve = RESOLVE_IN_ROOT,
217*4882a593Smuzhiyun 		  .out.path = NULL,		.pass = true },
218*4882a593Smuzhiyun 		{ .name = "[in_root] chained absolute symlinks to /root",
219*4882a593Smuzhiyun 		  .path = "abscheeky/absself",	.how.resolve = RESOLVE_IN_ROOT,
220*4882a593Smuzhiyun 		  .out.path = NULL,		.pass = true },
221*4882a593Smuzhiyun 		{ .name = "[in_root] '..' at root",
222*4882a593Smuzhiyun 		  .path = "..",			.how.resolve = RESOLVE_IN_ROOT,
223*4882a593Smuzhiyun 		  .out.path = NULL,		.pass = true },
224*4882a593Smuzhiyun 		{ .name = "[in_root] '../root' at root",
225*4882a593Smuzhiyun 		  .path = "../root/",		.how.resolve = RESOLVE_IN_ROOT,
226*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
227*4882a593Smuzhiyun 		{ .name = "[in_root] relative symlink containing '..' above root",
228*4882a593Smuzhiyun 		  .path = "cheeky/self",	.how.resolve = RESOLVE_IN_ROOT,
229*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
230*4882a593Smuzhiyun 		{ .name = "[in_root] garbage link to /root",
231*4882a593Smuzhiyun 		  .path = "cheeky/garbageself",	.how.resolve = RESOLVE_IN_ROOT,
232*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
233*4882a593Smuzhiyun 		{ .name = "[in_root] chained garbage links to /root",
234*4882a593Smuzhiyun 		  .path = "abscheeky/garbageself", .how.resolve = RESOLVE_IN_ROOT,
235*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
236*4882a593Smuzhiyun 		{ .name = "[in_root] relative path to 'root'",
237*4882a593Smuzhiyun 		  .path = "root",		.how.resolve = RESOLVE_IN_ROOT,
238*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
239*4882a593Smuzhiyun 		{ .name = "[in_root] relative path to 'etc'",
240*4882a593Smuzhiyun 		  .path = "etc",		.how.resolve = RESOLVE_IN_ROOT,
241*4882a593Smuzhiyun 		  .out.path = "etc",		.pass = true },
242*4882a593Smuzhiyun 		{ .name = "[in_root] relative path to 'etc/passwd'",
243*4882a593Smuzhiyun 		  .path = "etc/passwd",		.how.resolve = RESOLVE_IN_ROOT,
244*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
245*4882a593Smuzhiyun 		{ .name = "[in_root] relative symlink to 'etc/passwd'",
246*4882a593Smuzhiyun 		  .path = "relsym",		.how.resolve = RESOLVE_IN_ROOT,
247*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
248*4882a593Smuzhiyun 		{ .name = "[in_root] chained-'..' relative symlink to 'etc/passwd'",
249*4882a593Smuzhiyun 		  .path = "cheeky/passwd",	.how.resolve = RESOLVE_IN_ROOT,
250*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
251*4882a593Smuzhiyun 		{ .name = "[in_root] chained-'..' absolute + relative symlink to 'etc/passwd'",
252*4882a593Smuzhiyun 		  .path = "abscheeky/passwd",	.how.resolve = RESOLVE_IN_ROOT,
253*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
254*4882a593Smuzhiyun 		{ .name = "[in_root] absolute symlink to 'etc/passwd'",
255*4882a593Smuzhiyun 		  .path = "abssym",		.how.resolve = RESOLVE_IN_ROOT,
256*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
257*4882a593Smuzhiyun 		{ .name = "[in_root] absolute path 'etc/passwd'",
258*4882a593Smuzhiyun 		  .path = "/etc/passwd",	.how.resolve = RESOLVE_IN_ROOT,
259*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
260*4882a593Smuzhiyun 		{ .name = "[in_root] cheeky absolute path 'etc/passwd'",
261*4882a593Smuzhiyun 		  .path = "cheeky/abspasswd",	.how.resolve = RESOLVE_IN_ROOT,
262*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
263*4882a593Smuzhiyun 		{ .name = "[in_root] chained cheeky absolute path 'etc/passwd'",
264*4882a593Smuzhiyun 		  .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_IN_ROOT,
265*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
266*4882a593Smuzhiyun 		{ .name = "[in_root] tricky '..'-chained symlink outside $root",
267*4882a593Smuzhiyun 		  .path = "cheeky/dotdotlink",	.how.resolve = RESOLVE_IN_ROOT,
268*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
269*4882a593Smuzhiyun 		{ .name = "[in_root] tricky absolute + '..'-chained symlink outside $root",
270*4882a593Smuzhiyun 		  .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT,
271*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
272*4882a593Smuzhiyun 		{ .name = "[in_root] tricky absolute path + absolute + '..'-chained symlink outside $root",
273*4882a593Smuzhiyun 		  .path = "/../../../../abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT,
274*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
275*4882a593Smuzhiyun 		{ .name = "[in_root] tricky garbage link outside $root",
276*4882a593Smuzhiyun 		  .path = "cheeky/garbagelink",	.how.resolve = RESOLVE_IN_ROOT,
277*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
278*4882a593Smuzhiyun 		{ .name = "[in_root] tricky absolute + garbage link outside $root",
279*4882a593Smuzhiyun 		  .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT,
280*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
281*4882a593Smuzhiyun 		{ .name = "[in_root] tricky absolute path + absolute + garbage link outside $root",
282*4882a593Smuzhiyun 		  .path = "/../../../../abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT,
283*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
284*4882a593Smuzhiyun 		/* O_CREAT should handle trailing symlinks correctly. */
285*4882a593Smuzhiyun 		{ .name = "[in_root] O_CREAT of relative path inside $root",
286*4882a593Smuzhiyun 		  .path = "newfile1",		.how.flags = O_CREAT,
287*4882a593Smuzhiyun 						.how.mode = 0700,
288*4882a593Smuzhiyun 						.how.resolve = RESOLVE_IN_ROOT,
289*4882a593Smuzhiyun 		  .out.path = "newfile1",	.pass = true },
290*4882a593Smuzhiyun 		{ .name = "[in_root] O_CREAT of absolute path",
291*4882a593Smuzhiyun 		  .path = "/newfile2",		.how.flags = O_CREAT,
292*4882a593Smuzhiyun 						.how.mode = 0700,
293*4882a593Smuzhiyun 						.how.resolve = RESOLVE_IN_ROOT,
294*4882a593Smuzhiyun 		  .out.path = "newfile2",	.pass = true },
295*4882a593Smuzhiyun 		{ .name = "[in_root] O_CREAT of tricky symlink outside root",
296*4882a593Smuzhiyun 		  .path = "/creatlink",		.how.flags = O_CREAT,
297*4882a593Smuzhiyun 						.how.mode = 0700,
298*4882a593Smuzhiyun 						.how.resolve = RESOLVE_IN_ROOT,
299*4882a593Smuzhiyun 		  .out.path = "newfile3",	.pass = true },
300*4882a593Smuzhiyun 
301*4882a593Smuzhiyun 		/** RESOLVE_NO_XDEV **/
302*4882a593Smuzhiyun 		/* Crossing *down* into a mountpoint is disallowed. */
303*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross into $mnt",
304*4882a593Smuzhiyun 		  .path = "mnt",		.how.resolve = RESOLVE_NO_XDEV,
305*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
306*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross into $mnt/",
307*4882a593Smuzhiyun 		  .path = "mnt/",		.how.resolve = RESOLVE_NO_XDEV,
308*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
309*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross into $mnt/.",
310*4882a593Smuzhiyun 		  .path = "mnt/.",		.how.resolve = RESOLVE_NO_XDEV,
311*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
312*4882a593Smuzhiyun 		/* Crossing *up* out of a mountpoint is disallowed. */
313*4882a593Smuzhiyun 		{ .name = "[no_xdev] goto mountpoint root",
314*4882a593Smuzhiyun 		  .dir = "mnt", .path = ".",	.how.resolve = RESOLVE_NO_XDEV,
315*4882a593Smuzhiyun 		  .out.path = "mnt",		.pass = true },
316*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross up through '..'",
317*4882a593Smuzhiyun 		  .dir = "mnt", .path = "..",	.how.resolve = RESOLVE_NO_XDEV,
318*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
319*4882a593Smuzhiyun 		{ .name = "[no_xdev] temporary cross up through '..'",
320*4882a593Smuzhiyun 		  .dir = "mnt", .path = "../mnt", .how.resolve = RESOLVE_NO_XDEV,
321*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
322*4882a593Smuzhiyun 		{ .name = "[no_xdev] temporary relative symlink cross up",
323*4882a593Smuzhiyun 		  .dir = "mnt", .path = "self",	.how.resolve = RESOLVE_NO_XDEV,
324*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
325*4882a593Smuzhiyun 		{ .name = "[no_xdev] temporary absolute symlink cross up",
326*4882a593Smuzhiyun 		  .dir = "mnt", .path = "absself", .how.resolve = RESOLVE_NO_XDEV,
327*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
328*4882a593Smuzhiyun 		/* Jumping to "/" is ok, but later components cannot cross. */
329*4882a593Smuzhiyun 		{ .name = "[no_xdev] jump to / directly",
330*4882a593Smuzhiyun 		  .dir = "mnt", .path = "/",	.how.resolve = RESOLVE_NO_XDEV,
331*4882a593Smuzhiyun 		  .out.path = "/",		.pass = true },
332*4882a593Smuzhiyun 		{ .name = "[no_xdev] jump to / (from /) directly",
333*4882a593Smuzhiyun 		  .dir = "/", .path = "/",	.how.resolve = RESOLVE_NO_XDEV,
334*4882a593Smuzhiyun 		  .out.path = "/",		.pass = true },
335*4882a593Smuzhiyun 		{ .name = "[no_xdev] jump to / then proc",
336*4882a593Smuzhiyun 		  .path = "/proc/1",		.how.resolve = RESOLVE_NO_XDEV,
337*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
338*4882a593Smuzhiyun 		{ .name = "[no_xdev] jump to / then tmp",
339*4882a593Smuzhiyun 		  .path = "/tmp",		.how.resolve = RESOLVE_NO_XDEV,
340*4882a593Smuzhiyun 		  .out.err = -EXDEV,		.pass = false },
341*4882a593Smuzhiyun 		/* Magic-links are blocked since they can switch vfsmounts. */
342*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross through magic-link to self/root",
343*4882a593Smuzhiyun 		  .dir = "/proc", .path = "self/root", 	.how.resolve = RESOLVE_NO_XDEV,
344*4882a593Smuzhiyun 		  .out.err = -EXDEV,			.pass = false },
345*4882a593Smuzhiyun 		{ .name = "[no_xdev] cross through magic-link to self/cwd",
346*4882a593Smuzhiyun 		  .dir = "/proc", .path = "self/cwd",	.how.resolve = RESOLVE_NO_XDEV,
347*4882a593Smuzhiyun 		  .out.err = -EXDEV,			.pass = false },
348*4882a593Smuzhiyun 		/* Except magic-link jumps inside the same vfsmount. */
349*4882a593Smuzhiyun 		{ .name = "[no_xdev] jump through magic-link to same procfs",
350*4882a593Smuzhiyun 		  .dir = "/proc", .path = hardcoded_fdpath, .how.resolve = RESOLVE_NO_XDEV,
351*4882a593Smuzhiyun 		  .out.path = "/proc",			    .pass = true, },
352*4882a593Smuzhiyun 
353*4882a593Smuzhiyun 		/** RESOLVE_NO_MAGICLINKS **/
354*4882a593Smuzhiyun 		/* Regular symlinks should work. */
355*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] ordinary relative symlink",
356*4882a593Smuzhiyun 		  .path = "relsym",		.how.resolve = RESOLVE_NO_MAGICLINKS,
357*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
358*4882a593Smuzhiyun 		/* Magic-links should not work. */
359*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] symlink to magic-link",
360*4882a593Smuzhiyun 		  .path = "procexe",		.how.resolve = RESOLVE_NO_MAGICLINKS,
361*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
362*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] normal path to magic-link",
363*4882a593Smuzhiyun 		  .path = "/proc/self/exe",	.how.resolve = RESOLVE_NO_MAGICLINKS,
364*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
365*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] normal path to magic-link with O_NOFOLLOW",
366*4882a593Smuzhiyun 		  .path = "/proc/self/exe",	.how.flags = O_NOFOLLOW,
367*4882a593Smuzhiyun 						.how.resolve = RESOLVE_NO_MAGICLINKS,
368*4882a593Smuzhiyun 		  .out.path = procselfexe,	.pass = true },
369*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] symlink to magic-link path component",
370*4882a593Smuzhiyun 		  .path = "procroot/etc",	.how.resolve = RESOLVE_NO_MAGICLINKS,
371*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
372*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] magic-link path component",
373*4882a593Smuzhiyun 		  .path = "/proc/self/root/etc", .how.resolve = RESOLVE_NO_MAGICLINKS,
374*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
375*4882a593Smuzhiyun 		{ .name = "[no_magiclinks] magic-link path component with O_NOFOLLOW",
376*4882a593Smuzhiyun 		  .path = "/proc/self/root/etc", .how.flags = O_NOFOLLOW,
377*4882a593Smuzhiyun 						 .how.resolve = RESOLVE_NO_MAGICLINKS,
378*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
379*4882a593Smuzhiyun 
380*4882a593Smuzhiyun 		/** RESOLVE_NO_SYMLINKS **/
381*4882a593Smuzhiyun 		/* Normal paths should work. */
382*4882a593Smuzhiyun 		{ .name = "[no_symlinks] ordinary path to '.'",
383*4882a593Smuzhiyun 		  .path = ".",			.how.resolve = RESOLVE_NO_SYMLINKS,
384*4882a593Smuzhiyun 		  .out.path = NULL,		.pass = true },
385*4882a593Smuzhiyun 		{ .name = "[no_symlinks] ordinary path to 'root'",
386*4882a593Smuzhiyun 		  .path = "root",		.how.resolve = RESOLVE_NO_SYMLINKS,
387*4882a593Smuzhiyun 		  .out.path = "root",		.pass = true },
388*4882a593Smuzhiyun 		{ .name = "[no_symlinks] ordinary path to 'etc'",
389*4882a593Smuzhiyun 		  .path = "etc",		.how.resolve = RESOLVE_NO_SYMLINKS,
390*4882a593Smuzhiyun 		  .out.path = "etc",		.pass = true },
391*4882a593Smuzhiyun 		{ .name = "[no_symlinks] ordinary path to 'etc/passwd'",
392*4882a593Smuzhiyun 		  .path = "etc/passwd",		.how.resolve = RESOLVE_NO_SYMLINKS,
393*4882a593Smuzhiyun 		  .out.path = "etc/passwd",	.pass = true },
394*4882a593Smuzhiyun 		/* Regular symlinks are blocked. */
395*4882a593Smuzhiyun 		{ .name = "[no_symlinks] relative symlink target",
396*4882a593Smuzhiyun 		  .path = "relsym",		.how.resolve = RESOLVE_NO_SYMLINKS,
397*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
398*4882a593Smuzhiyun 		{ .name = "[no_symlinks] relative symlink component",
399*4882a593Smuzhiyun 		  .path = "reletc/passwd",	.how.resolve = RESOLVE_NO_SYMLINKS,
400*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
401*4882a593Smuzhiyun 		{ .name = "[no_symlinks] absolute symlink target",
402*4882a593Smuzhiyun 		  .path = "abssym",		.how.resolve = RESOLVE_NO_SYMLINKS,
403*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
404*4882a593Smuzhiyun 		{ .name = "[no_symlinks] absolute symlink component",
405*4882a593Smuzhiyun 		  .path = "absetc/passwd",	.how.resolve = RESOLVE_NO_SYMLINKS,
406*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
407*4882a593Smuzhiyun 		{ .name = "[no_symlinks] cheeky garbage link",
408*4882a593Smuzhiyun 		  .path = "cheeky/garbagelink",	.how.resolve = RESOLVE_NO_SYMLINKS,
409*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
410*4882a593Smuzhiyun 		{ .name = "[no_symlinks] cheeky absolute + garbage link",
411*4882a593Smuzhiyun 		  .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_NO_SYMLINKS,
412*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
413*4882a593Smuzhiyun 		{ .name = "[no_symlinks] cheeky absolute + absolute symlink",
414*4882a593Smuzhiyun 		  .path = "abscheeky/absself",	.how.resolve = RESOLVE_NO_SYMLINKS,
415*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
416*4882a593Smuzhiyun 		/* Trailing symlinks with NO_FOLLOW. */
417*4882a593Smuzhiyun 		{ .name = "[no_symlinks] relative symlink with O_NOFOLLOW",
418*4882a593Smuzhiyun 		  .path = "relsym",		.how.flags = O_NOFOLLOW,
419*4882a593Smuzhiyun 						.how.resolve = RESOLVE_NO_SYMLINKS,
420*4882a593Smuzhiyun 		  .out.path = "relsym",		.pass = true },
421*4882a593Smuzhiyun 		{ .name = "[no_symlinks] absolute symlink with O_NOFOLLOW",
422*4882a593Smuzhiyun 		  .path = "abssym",		.how.flags = O_NOFOLLOW,
423*4882a593Smuzhiyun 						.how.resolve = RESOLVE_NO_SYMLINKS,
424*4882a593Smuzhiyun 		  .out.path = "abssym",		.pass = true },
425*4882a593Smuzhiyun 		{ .name = "[no_symlinks] trailing symlink with O_NOFOLLOW",
426*4882a593Smuzhiyun 		  .path = "cheeky/garbagelink",	.how.flags = O_NOFOLLOW,
427*4882a593Smuzhiyun 						.how.resolve = RESOLVE_NO_SYMLINKS,
428*4882a593Smuzhiyun 		  .out.path = "cheeky/garbagelink", .pass = true },
429*4882a593Smuzhiyun 		{ .name = "[no_symlinks] multiple symlink components with O_NOFOLLOW",
430*4882a593Smuzhiyun 		  .path = "abscheeky/absself",	.how.flags = O_NOFOLLOW,
431*4882a593Smuzhiyun 						.how.resolve = RESOLVE_NO_SYMLINKS,
432*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
433*4882a593Smuzhiyun 		{ .name = "[no_symlinks] multiple symlink (and garbage link) components with O_NOFOLLOW",
434*4882a593Smuzhiyun 		  .path = "abscheeky/garbagelink", .how.flags = O_NOFOLLOW,
435*4882a593Smuzhiyun 						   .how.resolve = RESOLVE_NO_SYMLINKS,
436*4882a593Smuzhiyun 		  .out.err = -ELOOP,		.pass = false },
437*4882a593Smuzhiyun 	};
438*4882a593Smuzhiyun 
439*4882a593Smuzhiyun 	BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_OPATH_TESTS);
440*4882a593Smuzhiyun 
441*4882a593Smuzhiyun 	for (int i = 0; i < ARRAY_LEN(tests); i++) {
442*4882a593Smuzhiyun 		int dfd, fd;
443*4882a593Smuzhiyun 		char *fdpath = NULL;
444*4882a593Smuzhiyun 		bool failed;
445*4882a593Smuzhiyun 		void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
446*4882a593Smuzhiyun 		struct basic_test *test = &tests[i];
447*4882a593Smuzhiyun 
448*4882a593Smuzhiyun 		if (!openat2_supported) {
449*4882a593Smuzhiyun 			ksft_print_msg("openat2(2) unsupported\n");
450*4882a593Smuzhiyun 			resultfn = ksft_test_result_skip;
451*4882a593Smuzhiyun 			goto skip;
452*4882a593Smuzhiyun 		}
453*4882a593Smuzhiyun 
454*4882a593Smuzhiyun 		/* Auto-set O_PATH. */
455*4882a593Smuzhiyun 		if (!(test->how.flags & O_CREAT))
456*4882a593Smuzhiyun 			test->how.flags |= O_PATH;
457*4882a593Smuzhiyun 
458*4882a593Smuzhiyun 		if (test->dir)
459*4882a593Smuzhiyun 			dfd = openat(rootfd, test->dir, O_PATH | O_DIRECTORY);
460*4882a593Smuzhiyun 		else
461*4882a593Smuzhiyun 			dfd = dup(rootfd);
462*4882a593Smuzhiyun 		E_assert(dfd, "failed to openat root '%s': %m", test->dir);
463*4882a593Smuzhiyun 
464*4882a593Smuzhiyun 		E_dup2(dfd, hardcoded_fd);
465*4882a593Smuzhiyun 
466*4882a593Smuzhiyun 		fd = sys_openat2(dfd, test->path, &test->how);
467*4882a593Smuzhiyun 		if (test->pass)
468*4882a593Smuzhiyun 			failed = (fd < 0 || !fdequal(fd, rootfd, test->out.path));
469*4882a593Smuzhiyun 		else
470*4882a593Smuzhiyun 			failed = (fd != test->out.err);
471*4882a593Smuzhiyun 		if (fd >= 0) {
472*4882a593Smuzhiyun 			fdpath = fdreadlink(fd);
473*4882a593Smuzhiyun 			close(fd);
474*4882a593Smuzhiyun 		}
475*4882a593Smuzhiyun 		close(dfd);
476*4882a593Smuzhiyun 
477*4882a593Smuzhiyun 		if (failed) {
478*4882a593Smuzhiyun 			resultfn = ksft_test_result_fail;
479*4882a593Smuzhiyun 
480*4882a593Smuzhiyun 			ksft_print_msg("openat2 unexpectedly returned ");
481*4882a593Smuzhiyun 			if (fdpath)
482*4882a593Smuzhiyun 				ksft_print_msg("%d['%s']\n", fd, fdpath);
483*4882a593Smuzhiyun 			else
484*4882a593Smuzhiyun 				ksft_print_msg("%d (%s)\n", fd, strerror(-fd));
485*4882a593Smuzhiyun 		}
486*4882a593Smuzhiyun 
487*4882a593Smuzhiyun skip:
488*4882a593Smuzhiyun 		if (test->pass)
489*4882a593Smuzhiyun 			resultfn("%s gives path '%s'\n", test->name,
490*4882a593Smuzhiyun 				 test->out.path ?: ".");
491*4882a593Smuzhiyun 		else
492*4882a593Smuzhiyun 			resultfn("%s fails with %d (%s)\n", test->name,
493*4882a593Smuzhiyun 				 test->out.err, strerror(-test->out.err));
494*4882a593Smuzhiyun 
495*4882a593Smuzhiyun 		fflush(stdout);
496*4882a593Smuzhiyun 		free(fdpath);
497*4882a593Smuzhiyun 	}
498*4882a593Smuzhiyun 
499*4882a593Smuzhiyun 	free(procselfexe);
500*4882a593Smuzhiyun 	close(rootfd);
501*4882a593Smuzhiyun 
502*4882a593Smuzhiyun 	free(hardcoded_fdpath);
503*4882a593Smuzhiyun 	close(hardcoded_fd);
504*4882a593Smuzhiyun }
505*4882a593Smuzhiyun 
506*4882a593Smuzhiyun #define NUM_TESTS NUM_OPENAT2_OPATH_TESTS
507*4882a593Smuzhiyun 
main(int argc,char ** argv)508*4882a593Smuzhiyun int main(int argc, char **argv)
509*4882a593Smuzhiyun {
510*4882a593Smuzhiyun 	ksft_print_header();
511*4882a593Smuzhiyun 	ksft_set_plan(NUM_TESTS);
512*4882a593Smuzhiyun 
513*4882a593Smuzhiyun 	/* NOTE: We should be checking for CAP_SYS_ADMIN here... */
514*4882a593Smuzhiyun 	if (geteuid() != 0)
515*4882a593Smuzhiyun 		ksft_exit_skip("all tests require euid == 0\n");
516*4882a593Smuzhiyun 
517*4882a593Smuzhiyun 	test_openat2_opath_tests();
518*4882a593Smuzhiyun 
519*4882a593Smuzhiyun 	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
520*4882a593Smuzhiyun 		ksft_exit_fail();
521*4882a593Smuzhiyun 	else
522*4882a593Smuzhiyun 		ksft_exit_pass();
523*4882a593Smuzhiyun }
524