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