162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Author: Aleksa Sarai <cyphar@cyphar.com> 462306a36Sopenharmony_ci * Copyright (C) 2018-2019 SUSE LLC. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#define _GNU_SOURCE 862306a36Sopenharmony_ci#include <fcntl.h> 962306a36Sopenharmony_ci#include <sched.h> 1062306a36Sopenharmony_ci#include <sys/stat.h> 1162306a36Sopenharmony_ci#include <sys/types.h> 1262306a36Sopenharmony_ci#include <sys/mount.h> 1362306a36Sopenharmony_ci#include <stdlib.h> 1462306a36Sopenharmony_ci#include <stdbool.h> 1562306a36Sopenharmony_ci#include <string.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "../kselftest.h" 1862306a36Sopenharmony_ci#include "helpers.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* 2162306a36Sopenharmony_ci * Construct a test directory with the following structure: 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * root/ 2462306a36Sopenharmony_ci * |-- procexe -> /proc/self/exe 2562306a36Sopenharmony_ci * |-- procroot -> /proc/self/root 2662306a36Sopenharmony_ci * |-- root/ 2762306a36Sopenharmony_ci * |-- mnt/ [mountpoint] 2862306a36Sopenharmony_ci * | |-- self -> ../mnt/ 2962306a36Sopenharmony_ci * | `-- absself -> /mnt/ 3062306a36Sopenharmony_ci * |-- etc/ 3162306a36Sopenharmony_ci * | `-- passwd 3262306a36Sopenharmony_ci * |-- creatlink -> /newfile3 3362306a36Sopenharmony_ci * |-- reletc -> etc/ 3462306a36Sopenharmony_ci * |-- relsym -> etc/passwd 3562306a36Sopenharmony_ci * |-- absetc -> /etc/ 3662306a36Sopenharmony_ci * |-- abssym -> /etc/passwd 3762306a36Sopenharmony_ci * |-- abscheeky -> /cheeky 3862306a36Sopenharmony_ci * `-- cheeky/ 3962306a36Sopenharmony_ci * |-- absself -> / 4062306a36Sopenharmony_ci * |-- self -> ../../root/ 4162306a36Sopenharmony_ci * |-- garbageself -> /../../root/ 4262306a36Sopenharmony_ci * |-- passwd -> ../cheeky/../cheeky/../etc/../etc/passwd 4362306a36Sopenharmony_ci * |-- abspasswd -> /../cheeky/../cheeky/../etc/../etc/passwd 4462306a36Sopenharmony_ci * |-- dotdotlink -> ../../../../../../../../../../../../../../etc/passwd 4562306a36Sopenharmony_ci * `-- garbagelink -> /../../../../../../../../../../../../../../etc/passwd 4662306a36Sopenharmony_ci */ 4762306a36Sopenharmony_ciint setup_testdir(void) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci int dfd, tmpfd; 5062306a36Sopenharmony_ci char dirname[] = "/tmp/ksft-openat2-testdir.XXXXXX"; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci /* Unshare and make /tmp a new directory. */ 5362306a36Sopenharmony_ci E_unshare(CLONE_NEWNS); 5462306a36Sopenharmony_ci E_mount("", "/tmp", "", MS_PRIVATE, ""); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* Make the top-level directory. */ 5762306a36Sopenharmony_ci if (!mkdtemp(dirname)) 5862306a36Sopenharmony_ci ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n"); 5962306a36Sopenharmony_ci dfd = open(dirname, O_PATH | O_DIRECTORY); 6062306a36Sopenharmony_ci if (dfd < 0) 6162306a36Sopenharmony_ci ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n"); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* A sub-directory which is actually used for tests. */ 6462306a36Sopenharmony_ci E_mkdirat(dfd, "root", 0755); 6562306a36Sopenharmony_ci tmpfd = openat(dfd, "root", O_PATH | O_DIRECTORY); 6662306a36Sopenharmony_ci if (tmpfd < 0) 6762306a36Sopenharmony_ci ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n"); 6862306a36Sopenharmony_ci close(dfd); 6962306a36Sopenharmony_ci dfd = tmpfd; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci E_symlinkat("/proc/self/exe", dfd, "procexe"); 7262306a36Sopenharmony_ci E_symlinkat("/proc/self/root", dfd, "procroot"); 7362306a36Sopenharmony_ci E_mkdirat(dfd, "root", 0755); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* There is no mountat(2), so use chdir. */ 7662306a36Sopenharmony_ci E_mkdirat(dfd, "mnt", 0755); 7762306a36Sopenharmony_ci E_fchdir(dfd); 7862306a36Sopenharmony_ci E_mount("tmpfs", "./mnt", "tmpfs", MS_NOSUID | MS_NODEV, ""); 7962306a36Sopenharmony_ci E_symlinkat("../mnt/", dfd, "mnt/self"); 8062306a36Sopenharmony_ci E_symlinkat("/mnt/", dfd, "mnt/absself"); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci E_mkdirat(dfd, "etc", 0755); 8362306a36Sopenharmony_ci E_touchat(dfd, "etc/passwd"); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci E_symlinkat("/newfile3", dfd, "creatlink"); 8662306a36Sopenharmony_ci E_symlinkat("etc/", dfd, "reletc"); 8762306a36Sopenharmony_ci E_symlinkat("etc/passwd", dfd, "relsym"); 8862306a36Sopenharmony_ci E_symlinkat("/etc/", dfd, "absetc"); 8962306a36Sopenharmony_ci E_symlinkat("/etc/passwd", dfd, "abssym"); 9062306a36Sopenharmony_ci E_symlinkat("/cheeky", dfd, "abscheeky"); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci E_mkdirat(dfd, "cheeky", 0755); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci E_symlinkat("/", dfd, "cheeky/absself"); 9562306a36Sopenharmony_ci E_symlinkat("../../root/", dfd, "cheeky/self"); 9662306a36Sopenharmony_ci E_symlinkat("/../../root/", dfd, "cheeky/garbageself"); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci E_symlinkat("../cheeky/../etc/../etc/passwd", dfd, "cheeky/passwd"); 9962306a36Sopenharmony_ci E_symlinkat("/../cheeky/../etc/../etc/passwd", dfd, "cheeky/abspasswd"); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci E_symlinkat("../../../../../../../../../../../../../../etc/passwd", 10262306a36Sopenharmony_ci dfd, "cheeky/dotdotlink"); 10362306a36Sopenharmony_ci E_symlinkat("/../../../../../../../../../../../../../../etc/passwd", 10462306a36Sopenharmony_ci dfd, "cheeky/garbagelink"); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return dfd; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistruct basic_test { 11062306a36Sopenharmony_ci const char *name; 11162306a36Sopenharmony_ci const char *dir; 11262306a36Sopenharmony_ci const char *path; 11362306a36Sopenharmony_ci struct open_how how; 11462306a36Sopenharmony_ci bool pass; 11562306a36Sopenharmony_ci union { 11662306a36Sopenharmony_ci int err; 11762306a36Sopenharmony_ci const char *path; 11862306a36Sopenharmony_ci } out; 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci#define NUM_OPENAT2_OPATH_TESTS 88 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_civoid test_openat2_opath_tests(void) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci int rootfd, hardcoded_fd; 12662306a36Sopenharmony_ci char *procselfexe, *hardcoded_fdpath; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci E_asprintf(&procselfexe, "/proc/%d/exe", getpid()); 12962306a36Sopenharmony_ci rootfd = setup_testdir(); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci hardcoded_fd = open("/dev/null", O_RDONLY); 13262306a36Sopenharmony_ci E_assert(hardcoded_fd >= 0, "open fd to hardcode"); 13362306a36Sopenharmony_ci E_asprintf(&hardcoded_fdpath, "self/fd/%d", hardcoded_fd); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci struct basic_test tests[] = { 13662306a36Sopenharmony_ci /** RESOLVE_BENEATH **/ 13762306a36Sopenharmony_ci /* Attempts to cross dirfd should be blocked. */ 13862306a36Sopenharmony_ci { .name = "[beneath] jump to /", 13962306a36Sopenharmony_ci .path = "/", .how.resolve = RESOLVE_BENEATH, 14062306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 14162306a36Sopenharmony_ci { .name = "[beneath] absolute link to $root", 14262306a36Sopenharmony_ci .path = "cheeky/absself", .how.resolve = RESOLVE_BENEATH, 14362306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 14462306a36Sopenharmony_ci { .name = "[beneath] chained absolute links to $root", 14562306a36Sopenharmony_ci .path = "abscheeky/absself", .how.resolve = RESOLVE_BENEATH, 14662306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 14762306a36Sopenharmony_ci { .name = "[beneath] jump outside $root", 14862306a36Sopenharmony_ci .path = "..", .how.resolve = RESOLVE_BENEATH, 14962306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 15062306a36Sopenharmony_ci { .name = "[beneath] temporary jump outside $root", 15162306a36Sopenharmony_ci .path = "../root/", .how.resolve = RESOLVE_BENEATH, 15262306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 15362306a36Sopenharmony_ci { .name = "[beneath] symlink temporary jump outside $root", 15462306a36Sopenharmony_ci .path = "cheeky/self", .how.resolve = RESOLVE_BENEATH, 15562306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 15662306a36Sopenharmony_ci { .name = "[beneath] chained symlink temporary jump outside $root", 15762306a36Sopenharmony_ci .path = "abscheeky/self", .how.resolve = RESOLVE_BENEATH, 15862306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 15962306a36Sopenharmony_ci { .name = "[beneath] garbage links to $root", 16062306a36Sopenharmony_ci .path = "cheeky/garbageself", .how.resolve = RESOLVE_BENEATH, 16162306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 16262306a36Sopenharmony_ci { .name = "[beneath] chained garbage links to $root", 16362306a36Sopenharmony_ci .path = "abscheeky/garbageself", .how.resolve = RESOLVE_BENEATH, 16462306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 16562306a36Sopenharmony_ci /* Only relative paths that stay inside dirfd should work. */ 16662306a36Sopenharmony_ci { .name = "[beneath] ordinary path to 'root'", 16762306a36Sopenharmony_ci .path = "root", .how.resolve = RESOLVE_BENEATH, 16862306a36Sopenharmony_ci .out.path = "root", .pass = true }, 16962306a36Sopenharmony_ci { .name = "[beneath] ordinary path to 'etc'", 17062306a36Sopenharmony_ci .path = "etc", .how.resolve = RESOLVE_BENEATH, 17162306a36Sopenharmony_ci .out.path = "etc", .pass = true }, 17262306a36Sopenharmony_ci { .name = "[beneath] ordinary path to 'etc/passwd'", 17362306a36Sopenharmony_ci .path = "etc/passwd", .how.resolve = RESOLVE_BENEATH, 17462306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 17562306a36Sopenharmony_ci { .name = "[beneath] relative symlink inside $root", 17662306a36Sopenharmony_ci .path = "relsym", .how.resolve = RESOLVE_BENEATH, 17762306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 17862306a36Sopenharmony_ci { .name = "[beneath] chained-'..' relative symlink inside $root", 17962306a36Sopenharmony_ci .path = "cheeky/passwd", .how.resolve = RESOLVE_BENEATH, 18062306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 18162306a36Sopenharmony_ci { .name = "[beneath] absolute symlink component outside $root", 18262306a36Sopenharmony_ci .path = "abscheeky/passwd", .how.resolve = RESOLVE_BENEATH, 18362306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 18462306a36Sopenharmony_ci { .name = "[beneath] absolute symlink target outside $root", 18562306a36Sopenharmony_ci .path = "abssym", .how.resolve = RESOLVE_BENEATH, 18662306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 18762306a36Sopenharmony_ci { .name = "[beneath] absolute path outside $root", 18862306a36Sopenharmony_ci .path = "/etc/passwd", .how.resolve = RESOLVE_BENEATH, 18962306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 19062306a36Sopenharmony_ci { .name = "[beneath] cheeky absolute path outside $root", 19162306a36Sopenharmony_ci .path = "cheeky/abspasswd", .how.resolve = RESOLVE_BENEATH, 19262306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 19362306a36Sopenharmony_ci { .name = "[beneath] chained cheeky absolute path outside $root", 19462306a36Sopenharmony_ci .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_BENEATH, 19562306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 19662306a36Sopenharmony_ci /* Tricky paths should fail. */ 19762306a36Sopenharmony_ci { .name = "[beneath] tricky '..'-chained symlink outside $root", 19862306a36Sopenharmony_ci .path = "cheeky/dotdotlink", .how.resolve = RESOLVE_BENEATH, 19962306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 20062306a36Sopenharmony_ci { .name = "[beneath] tricky absolute + '..'-chained symlink outside $root", 20162306a36Sopenharmony_ci .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_BENEATH, 20262306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 20362306a36Sopenharmony_ci { .name = "[beneath] tricky garbage link outside $root", 20462306a36Sopenharmony_ci .path = "cheeky/garbagelink", .how.resolve = RESOLVE_BENEATH, 20562306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 20662306a36Sopenharmony_ci { .name = "[beneath] tricky absolute + garbage link outside $root", 20762306a36Sopenharmony_ci .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_BENEATH, 20862306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /** RESOLVE_IN_ROOT **/ 21162306a36Sopenharmony_ci /* All attempts to cross the dirfd will be scoped-to-root. */ 21262306a36Sopenharmony_ci { .name = "[in_root] jump to /", 21362306a36Sopenharmony_ci .path = "/", .how.resolve = RESOLVE_IN_ROOT, 21462306a36Sopenharmony_ci .out.path = NULL, .pass = true }, 21562306a36Sopenharmony_ci { .name = "[in_root] absolute symlink to /root", 21662306a36Sopenharmony_ci .path = "cheeky/absself", .how.resolve = RESOLVE_IN_ROOT, 21762306a36Sopenharmony_ci .out.path = NULL, .pass = true }, 21862306a36Sopenharmony_ci { .name = "[in_root] chained absolute symlinks to /root", 21962306a36Sopenharmony_ci .path = "abscheeky/absself", .how.resolve = RESOLVE_IN_ROOT, 22062306a36Sopenharmony_ci .out.path = NULL, .pass = true }, 22162306a36Sopenharmony_ci { .name = "[in_root] '..' at root", 22262306a36Sopenharmony_ci .path = "..", .how.resolve = RESOLVE_IN_ROOT, 22362306a36Sopenharmony_ci .out.path = NULL, .pass = true }, 22462306a36Sopenharmony_ci { .name = "[in_root] '../root' at root", 22562306a36Sopenharmony_ci .path = "../root/", .how.resolve = RESOLVE_IN_ROOT, 22662306a36Sopenharmony_ci .out.path = "root", .pass = true }, 22762306a36Sopenharmony_ci { .name = "[in_root] relative symlink containing '..' above root", 22862306a36Sopenharmony_ci .path = "cheeky/self", .how.resolve = RESOLVE_IN_ROOT, 22962306a36Sopenharmony_ci .out.path = "root", .pass = true }, 23062306a36Sopenharmony_ci { .name = "[in_root] garbage link to /root", 23162306a36Sopenharmony_ci .path = "cheeky/garbageself", .how.resolve = RESOLVE_IN_ROOT, 23262306a36Sopenharmony_ci .out.path = "root", .pass = true }, 23362306a36Sopenharmony_ci { .name = "[in_root] chained garbage links to /root", 23462306a36Sopenharmony_ci .path = "abscheeky/garbageself", .how.resolve = RESOLVE_IN_ROOT, 23562306a36Sopenharmony_ci .out.path = "root", .pass = true }, 23662306a36Sopenharmony_ci { .name = "[in_root] relative path to 'root'", 23762306a36Sopenharmony_ci .path = "root", .how.resolve = RESOLVE_IN_ROOT, 23862306a36Sopenharmony_ci .out.path = "root", .pass = true }, 23962306a36Sopenharmony_ci { .name = "[in_root] relative path to 'etc'", 24062306a36Sopenharmony_ci .path = "etc", .how.resolve = RESOLVE_IN_ROOT, 24162306a36Sopenharmony_ci .out.path = "etc", .pass = true }, 24262306a36Sopenharmony_ci { .name = "[in_root] relative path to 'etc/passwd'", 24362306a36Sopenharmony_ci .path = "etc/passwd", .how.resolve = RESOLVE_IN_ROOT, 24462306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 24562306a36Sopenharmony_ci { .name = "[in_root] relative symlink to 'etc/passwd'", 24662306a36Sopenharmony_ci .path = "relsym", .how.resolve = RESOLVE_IN_ROOT, 24762306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 24862306a36Sopenharmony_ci { .name = "[in_root] chained-'..' relative symlink to 'etc/passwd'", 24962306a36Sopenharmony_ci .path = "cheeky/passwd", .how.resolve = RESOLVE_IN_ROOT, 25062306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 25162306a36Sopenharmony_ci { .name = "[in_root] chained-'..' absolute + relative symlink to 'etc/passwd'", 25262306a36Sopenharmony_ci .path = "abscheeky/passwd", .how.resolve = RESOLVE_IN_ROOT, 25362306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 25462306a36Sopenharmony_ci { .name = "[in_root] absolute symlink to 'etc/passwd'", 25562306a36Sopenharmony_ci .path = "abssym", .how.resolve = RESOLVE_IN_ROOT, 25662306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 25762306a36Sopenharmony_ci { .name = "[in_root] absolute path 'etc/passwd'", 25862306a36Sopenharmony_ci .path = "/etc/passwd", .how.resolve = RESOLVE_IN_ROOT, 25962306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 26062306a36Sopenharmony_ci { .name = "[in_root] cheeky absolute path 'etc/passwd'", 26162306a36Sopenharmony_ci .path = "cheeky/abspasswd", .how.resolve = RESOLVE_IN_ROOT, 26262306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 26362306a36Sopenharmony_ci { .name = "[in_root] chained cheeky absolute path 'etc/passwd'", 26462306a36Sopenharmony_ci .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_IN_ROOT, 26562306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 26662306a36Sopenharmony_ci { .name = "[in_root] tricky '..'-chained symlink outside $root", 26762306a36Sopenharmony_ci .path = "cheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, 26862306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 26962306a36Sopenharmony_ci { .name = "[in_root] tricky absolute + '..'-chained symlink outside $root", 27062306a36Sopenharmony_ci .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, 27162306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 27262306a36Sopenharmony_ci { .name = "[in_root] tricky absolute path + absolute + '..'-chained symlink outside $root", 27362306a36Sopenharmony_ci .path = "/../../../../abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, 27462306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 27562306a36Sopenharmony_ci { .name = "[in_root] tricky garbage link outside $root", 27662306a36Sopenharmony_ci .path = "cheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, 27762306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 27862306a36Sopenharmony_ci { .name = "[in_root] tricky absolute + garbage link outside $root", 27962306a36Sopenharmony_ci .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, 28062306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 28162306a36Sopenharmony_ci { .name = "[in_root] tricky absolute path + absolute + garbage link outside $root", 28262306a36Sopenharmony_ci .path = "/../../../../abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, 28362306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 28462306a36Sopenharmony_ci /* O_CREAT should handle trailing symlinks correctly. */ 28562306a36Sopenharmony_ci { .name = "[in_root] O_CREAT of relative path inside $root", 28662306a36Sopenharmony_ci .path = "newfile1", .how.flags = O_CREAT, 28762306a36Sopenharmony_ci .how.mode = 0700, 28862306a36Sopenharmony_ci .how.resolve = RESOLVE_IN_ROOT, 28962306a36Sopenharmony_ci .out.path = "newfile1", .pass = true }, 29062306a36Sopenharmony_ci { .name = "[in_root] O_CREAT of absolute path", 29162306a36Sopenharmony_ci .path = "/newfile2", .how.flags = O_CREAT, 29262306a36Sopenharmony_ci .how.mode = 0700, 29362306a36Sopenharmony_ci .how.resolve = RESOLVE_IN_ROOT, 29462306a36Sopenharmony_ci .out.path = "newfile2", .pass = true }, 29562306a36Sopenharmony_ci { .name = "[in_root] O_CREAT of tricky symlink outside root", 29662306a36Sopenharmony_ci .path = "/creatlink", .how.flags = O_CREAT, 29762306a36Sopenharmony_ci .how.mode = 0700, 29862306a36Sopenharmony_ci .how.resolve = RESOLVE_IN_ROOT, 29962306a36Sopenharmony_ci .out.path = "newfile3", .pass = true }, 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /** RESOLVE_NO_XDEV **/ 30262306a36Sopenharmony_ci /* Crossing *down* into a mountpoint is disallowed. */ 30362306a36Sopenharmony_ci { .name = "[no_xdev] cross into $mnt", 30462306a36Sopenharmony_ci .path = "mnt", .how.resolve = RESOLVE_NO_XDEV, 30562306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 30662306a36Sopenharmony_ci { .name = "[no_xdev] cross into $mnt/", 30762306a36Sopenharmony_ci .path = "mnt/", .how.resolve = RESOLVE_NO_XDEV, 30862306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 30962306a36Sopenharmony_ci { .name = "[no_xdev] cross into $mnt/.", 31062306a36Sopenharmony_ci .path = "mnt/.", .how.resolve = RESOLVE_NO_XDEV, 31162306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 31262306a36Sopenharmony_ci /* Crossing *up* out of a mountpoint is disallowed. */ 31362306a36Sopenharmony_ci { .name = "[no_xdev] goto mountpoint root", 31462306a36Sopenharmony_ci .dir = "mnt", .path = ".", .how.resolve = RESOLVE_NO_XDEV, 31562306a36Sopenharmony_ci .out.path = "mnt", .pass = true }, 31662306a36Sopenharmony_ci { .name = "[no_xdev] cross up through '..'", 31762306a36Sopenharmony_ci .dir = "mnt", .path = "..", .how.resolve = RESOLVE_NO_XDEV, 31862306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 31962306a36Sopenharmony_ci { .name = "[no_xdev] temporary cross up through '..'", 32062306a36Sopenharmony_ci .dir = "mnt", .path = "../mnt", .how.resolve = RESOLVE_NO_XDEV, 32162306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 32262306a36Sopenharmony_ci { .name = "[no_xdev] temporary relative symlink cross up", 32362306a36Sopenharmony_ci .dir = "mnt", .path = "self", .how.resolve = RESOLVE_NO_XDEV, 32462306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 32562306a36Sopenharmony_ci { .name = "[no_xdev] temporary absolute symlink cross up", 32662306a36Sopenharmony_ci .dir = "mnt", .path = "absself", .how.resolve = RESOLVE_NO_XDEV, 32762306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 32862306a36Sopenharmony_ci /* Jumping to "/" is ok, but later components cannot cross. */ 32962306a36Sopenharmony_ci { .name = "[no_xdev] jump to / directly", 33062306a36Sopenharmony_ci .dir = "mnt", .path = "/", .how.resolve = RESOLVE_NO_XDEV, 33162306a36Sopenharmony_ci .out.path = "/", .pass = true }, 33262306a36Sopenharmony_ci { .name = "[no_xdev] jump to / (from /) directly", 33362306a36Sopenharmony_ci .dir = "/", .path = "/", .how.resolve = RESOLVE_NO_XDEV, 33462306a36Sopenharmony_ci .out.path = "/", .pass = true }, 33562306a36Sopenharmony_ci { .name = "[no_xdev] jump to / then proc", 33662306a36Sopenharmony_ci .path = "/proc/1", .how.resolve = RESOLVE_NO_XDEV, 33762306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 33862306a36Sopenharmony_ci { .name = "[no_xdev] jump to / then tmp", 33962306a36Sopenharmony_ci .path = "/tmp", .how.resolve = RESOLVE_NO_XDEV, 34062306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 34162306a36Sopenharmony_ci /* Magic-links are blocked since they can switch vfsmounts. */ 34262306a36Sopenharmony_ci { .name = "[no_xdev] cross through magic-link to self/root", 34362306a36Sopenharmony_ci .dir = "/proc", .path = "self/root", .how.resolve = RESOLVE_NO_XDEV, 34462306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 34562306a36Sopenharmony_ci { .name = "[no_xdev] cross through magic-link to self/cwd", 34662306a36Sopenharmony_ci .dir = "/proc", .path = "self/cwd", .how.resolve = RESOLVE_NO_XDEV, 34762306a36Sopenharmony_ci .out.err = -EXDEV, .pass = false }, 34862306a36Sopenharmony_ci /* Except magic-link jumps inside the same vfsmount. */ 34962306a36Sopenharmony_ci { .name = "[no_xdev] jump through magic-link to same procfs", 35062306a36Sopenharmony_ci .dir = "/proc", .path = hardcoded_fdpath, .how.resolve = RESOLVE_NO_XDEV, 35162306a36Sopenharmony_ci .out.path = "/proc", .pass = true, }, 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /** RESOLVE_NO_MAGICLINKS **/ 35462306a36Sopenharmony_ci /* Regular symlinks should work. */ 35562306a36Sopenharmony_ci { .name = "[no_magiclinks] ordinary relative symlink", 35662306a36Sopenharmony_ci .path = "relsym", .how.resolve = RESOLVE_NO_MAGICLINKS, 35762306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 35862306a36Sopenharmony_ci /* Magic-links should not work. */ 35962306a36Sopenharmony_ci { .name = "[no_magiclinks] symlink to magic-link", 36062306a36Sopenharmony_ci .path = "procexe", .how.resolve = RESOLVE_NO_MAGICLINKS, 36162306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 36262306a36Sopenharmony_ci { .name = "[no_magiclinks] normal path to magic-link", 36362306a36Sopenharmony_ci .path = "/proc/self/exe", .how.resolve = RESOLVE_NO_MAGICLINKS, 36462306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 36562306a36Sopenharmony_ci { .name = "[no_magiclinks] normal path to magic-link with O_NOFOLLOW", 36662306a36Sopenharmony_ci .path = "/proc/self/exe", .how.flags = O_NOFOLLOW, 36762306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_MAGICLINKS, 36862306a36Sopenharmony_ci .out.path = procselfexe, .pass = true }, 36962306a36Sopenharmony_ci { .name = "[no_magiclinks] symlink to magic-link path component", 37062306a36Sopenharmony_ci .path = "procroot/etc", .how.resolve = RESOLVE_NO_MAGICLINKS, 37162306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 37262306a36Sopenharmony_ci { .name = "[no_magiclinks] magic-link path component", 37362306a36Sopenharmony_ci .path = "/proc/self/root/etc", .how.resolve = RESOLVE_NO_MAGICLINKS, 37462306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 37562306a36Sopenharmony_ci { .name = "[no_magiclinks] magic-link path component with O_NOFOLLOW", 37662306a36Sopenharmony_ci .path = "/proc/self/root/etc", .how.flags = O_NOFOLLOW, 37762306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_MAGICLINKS, 37862306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci /** RESOLVE_NO_SYMLINKS **/ 38162306a36Sopenharmony_ci /* Normal paths should work. */ 38262306a36Sopenharmony_ci { .name = "[no_symlinks] ordinary path to '.'", 38362306a36Sopenharmony_ci .path = ".", .how.resolve = RESOLVE_NO_SYMLINKS, 38462306a36Sopenharmony_ci .out.path = NULL, .pass = true }, 38562306a36Sopenharmony_ci { .name = "[no_symlinks] ordinary path to 'root'", 38662306a36Sopenharmony_ci .path = "root", .how.resolve = RESOLVE_NO_SYMLINKS, 38762306a36Sopenharmony_ci .out.path = "root", .pass = true }, 38862306a36Sopenharmony_ci { .name = "[no_symlinks] ordinary path to 'etc'", 38962306a36Sopenharmony_ci .path = "etc", .how.resolve = RESOLVE_NO_SYMLINKS, 39062306a36Sopenharmony_ci .out.path = "etc", .pass = true }, 39162306a36Sopenharmony_ci { .name = "[no_symlinks] ordinary path to 'etc/passwd'", 39262306a36Sopenharmony_ci .path = "etc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, 39362306a36Sopenharmony_ci .out.path = "etc/passwd", .pass = true }, 39462306a36Sopenharmony_ci /* Regular symlinks are blocked. */ 39562306a36Sopenharmony_ci { .name = "[no_symlinks] relative symlink target", 39662306a36Sopenharmony_ci .path = "relsym", .how.resolve = RESOLVE_NO_SYMLINKS, 39762306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 39862306a36Sopenharmony_ci { .name = "[no_symlinks] relative symlink component", 39962306a36Sopenharmony_ci .path = "reletc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, 40062306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 40162306a36Sopenharmony_ci { .name = "[no_symlinks] absolute symlink target", 40262306a36Sopenharmony_ci .path = "abssym", .how.resolve = RESOLVE_NO_SYMLINKS, 40362306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 40462306a36Sopenharmony_ci { .name = "[no_symlinks] absolute symlink component", 40562306a36Sopenharmony_ci .path = "absetc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, 40662306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 40762306a36Sopenharmony_ci { .name = "[no_symlinks] cheeky garbage link", 40862306a36Sopenharmony_ci .path = "cheeky/garbagelink", .how.resolve = RESOLVE_NO_SYMLINKS, 40962306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 41062306a36Sopenharmony_ci { .name = "[no_symlinks] cheeky absolute + garbage link", 41162306a36Sopenharmony_ci .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_NO_SYMLINKS, 41262306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 41362306a36Sopenharmony_ci { .name = "[no_symlinks] cheeky absolute + absolute symlink", 41462306a36Sopenharmony_ci .path = "abscheeky/absself", .how.resolve = RESOLVE_NO_SYMLINKS, 41562306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 41662306a36Sopenharmony_ci /* Trailing symlinks with NO_FOLLOW. */ 41762306a36Sopenharmony_ci { .name = "[no_symlinks] relative symlink with O_NOFOLLOW", 41862306a36Sopenharmony_ci .path = "relsym", .how.flags = O_NOFOLLOW, 41962306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_SYMLINKS, 42062306a36Sopenharmony_ci .out.path = "relsym", .pass = true }, 42162306a36Sopenharmony_ci { .name = "[no_symlinks] absolute symlink with O_NOFOLLOW", 42262306a36Sopenharmony_ci .path = "abssym", .how.flags = O_NOFOLLOW, 42362306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_SYMLINKS, 42462306a36Sopenharmony_ci .out.path = "abssym", .pass = true }, 42562306a36Sopenharmony_ci { .name = "[no_symlinks] trailing symlink with O_NOFOLLOW", 42662306a36Sopenharmony_ci .path = "cheeky/garbagelink", .how.flags = O_NOFOLLOW, 42762306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_SYMLINKS, 42862306a36Sopenharmony_ci .out.path = "cheeky/garbagelink", .pass = true }, 42962306a36Sopenharmony_ci { .name = "[no_symlinks] multiple symlink components with O_NOFOLLOW", 43062306a36Sopenharmony_ci .path = "abscheeky/absself", .how.flags = O_NOFOLLOW, 43162306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_SYMLINKS, 43262306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 43362306a36Sopenharmony_ci { .name = "[no_symlinks] multiple symlink (and garbage link) components with O_NOFOLLOW", 43462306a36Sopenharmony_ci .path = "abscheeky/garbagelink", .how.flags = O_NOFOLLOW, 43562306a36Sopenharmony_ci .how.resolve = RESOLVE_NO_SYMLINKS, 43662306a36Sopenharmony_ci .out.err = -ELOOP, .pass = false }, 43762306a36Sopenharmony_ci }; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_OPATH_TESTS); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci for (int i = 0; i < ARRAY_LEN(tests); i++) { 44262306a36Sopenharmony_ci int dfd, fd; 44362306a36Sopenharmony_ci char *fdpath = NULL; 44462306a36Sopenharmony_ci bool failed; 44562306a36Sopenharmony_ci void (*resultfn)(const char *msg, ...) = ksft_test_result_pass; 44662306a36Sopenharmony_ci struct basic_test *test = &tests[i]; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci if (!openat2_supported) { 44962306a36Sopenharmony_ci ksft_print_msg("openat2(2) unsupported\n"); 45062306a36Sopenharmony_ci resultfn = ksft_test_result_skip; 45162306a36Sopenharmony_ci goto skip; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci /* Auto-set O_PATH. */ 45562306a36Sopenharmony_ci if (!(test->how.flags & O_CREAT)) 45662306a36Sopenharmony_ci test->how.flags |= O_PATH; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci if (test->dir) 45962306a36Sopenharmony_ci dfd = openat(rootfd, test->dir, O_PATH | O_DIRECTORY); 46062306a36Sopenharmony_ci else 46162306a36Sopenharmony_ci dfd = dup(rootfd); 46262306a36Sopenharmony_ci E_assert(dfd, "failed to openat root '%s': %m", test->dir); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci E_dup2(dfd, hardcoded_fd); 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci fd = sys_openat2(dfd, test->path, &test->how); 46762306a36Sopenharmony_ci if (test->pass) 46862306a36Sopenharmony_ci failed = (fd < 0 || !fdequal(fd, rootfd, test->out.path)); 46962306a36Sopenharmony_ci else 47062306a36Sopenharmony_ci failed = (fd != test->out.err); 47162306a36Sopenharmony_ci if (fd >= 0) { 47262306a36Sopenharmony_ci fdpath = fdreadlink(fd); 47362306a36Sopenharmony_ci close(fd); 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci close(dfd); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci if (failed) { 47862306a36Sopenharmony_ci resultfn = ksft_test_result_fail; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci ksft_print_msg("openat2 unexpectedly returned "); 48162306a36Sopenharmony_ci if (fdpath) 48262306a36Sopenharmony_ci ksft_print_msg("%d['%s']\n", fd, fdpath); 48362306a36Sopenharmony_ci else 48462306a36Sopenharmony_ci ksft_print_msg("%d (%s)\n", fd, strerror(-fd)); 48562306a36Sopenharmony_ci } 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ciskip: 48862306a36Sopenharmony_ci if (test->pass) 48962306a36Sopenharmony_ci resultfn("%s gives path '%s'\n", test->name, 49062306a36Sopenharmony_ci test->out.path ?: "."); 49162306a36Sopenharmony_ci else 49262306a36Sopenharmony_ci resultfn("%s fails with %d (%s)\n", test->name, 49362306a36Sopenharmony_ci test->out.err, strerror(-test->out.err)); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci fflush(stdout); 49662306a36Sopenharmony_ci free(fdpath); 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci free(procselfexe); 50062306a36Sopenharmony_ci close(rootfd); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci free(hardcoded_fdpath); 50362306a36Sopenharmony_ci close(hardcoded_fd); 50462306a36Sopenharmony_ci} 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci#define NUM_TESTS NUM_OPENAT2_OPATH_TESTS 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ciint main(int argc, char **argv) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci ksft_print_header(); 51162306a36Sopenharmony_ci ksft_set_plan(NUM_TESTS); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* NOTE: We should be checking for CAP_SYS_ADMIN here... */ 51462306a36Sopenharmony_ci if (geteuid() != 0) 51562306a36Sopenharmony_ci ksft_exit_skip("all tests require euid == 0\n"); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci test_openat2_opath_tests(); 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 52062306a36Sopenharmony_ci ksft_exit_fail(); 52162306a36Sopenharmony_ci else 52262306a36Sopenharmony_ci ksft_exit_pass(); 52362306a36Sopenharmony_ci} 524