brintos

brintos / linux-shallow public Read only

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