| |
| |
| |
| |
| |
| |
| #define _GNU_SOURCE |
| #include <fcntl.h> |
| #include <sched.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/mount.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #include "../kselftest.h" |
| #include "helpers.h" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int setup_testdir(void) |
| { |
| <------>int dfd, tmpfd; |
| <------>char dirname[] = "/tmp/ksft-openat2-testdir.XXXXXX"; |
| |
| <------> |
| <------>E_unshare(CLONE_NEWNS); |
| <------>E_mount("", "/tmp", "", MS_PRIVATE, ""); |
| |
| <------> |
| <------>if (!mkdtemp(dirname)) |
| <------><------>ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n"); |
| <------>dfd = open(dirname, O_PATH | O_DIRECTORY); |
| <------>if (dfd < 0) |
| <------><------>ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n"); |
| |
| <------> |
| <------>E_mkdirat(dfd, "root", 0755); |
| <------>tmpfd = openat(dfd, "root", O_PATH | O_DIRECTORY); |
| <------>if (tmpfd < 0) |
| <------><------>ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n"); |
| <------>close(dfd); |
| <------>dfd = tmpfd; |
| |
| <------>E_symlinkat("/proc/self/exe", dfd, "procexe"); |
| <------>E_symlinkat("/proc/self/root", dfd, "procroot"); |
| <------>E_mkdirat(dfd, "root", 0755); |
| |
| <------> |
| <------>E_mkdirat(dfd, "mnt", 0755); |
| <------>E_fchdir(dfd); |
| <------>E_mount("tmpfs", "./mnt", "tmpfs", MS_NOSUID | MS_NODEV, ""); |
| <------>E_symlinkat("../mnt/", dfd, "mnt/self"); |
| <------>E_symlinkat("/mnt/", dfd, "mnt/absself"); |
| |
| <------>E_mkdirat(dfd, "etc", 0755); |
| <------>E_touchat(dfd, "etc/passwd"); |
| |
| <------>E_symlinkat("/newfile3", dfd, "creatlink"); |
| <------>E_symlinkat("etc/", dfd, "reletc"); |
| <------>E_symlinkat("etc/passwd", dfd, "relsym"); |
| <------>E_symlinkat("/etc/", dfd, "absetc"); |
| <------>E_symlinkat("/etc/passwd", dfd, "abssym"); |
| <------>E_symlinkat("/cheeky", dfd, "abscheeky"); |
| |
| <------>E_mkdirat(dfd, "cheeky", 0755); |
| |
| <------>E_symlinkat("/", dfd, "cheeky/absself"); |
| <------>E_symlinkat("../../root/", dfd, "cheeky/self"); |
| <------>E_symlinkat("/../../root/", dfd, "cheeky/garbageself"); |
| |
| <------>E_symlinkat("../cheeky/../etc/../etc/passwd", dfd, "cheeky/passwd"); |
| <------>E_symlinkat("/../cheeky/../etc/../etc/passwd", dfd, "cheeky/abspasswd"); |
| |
| <------>E_symlinkat("../../../../../../../../../../../../../../etc/passwd", |
| <------><------> dfd, "cheeky/dotdotlink"); |
| <------>E_symlinkat("/../../../../../../../../../../../../../../etc/passwd", |
| <------><------> dfd, "cheeky/garbagelink"); |
| |
| <------>return dfd; |
| } |
| |
| struct basic_test { |
| <------>const char *name; |
| <------>const char *dir; |
| <------>const char *path; |
| <------>struct open_how how; |
| <------>bool pass; |
| <------>union { |
| <------><------>int err; |
| <------><------>const char *path; |
| <------>} out; |
| }; |
| |
| #define NUM_OPENAT2_OPATH_TESTS 88 |
| |
| void test_openat2_opath_tests(void) |
| { |
| <------>int rootfd, hardcoded_fd; |
| <------>char *procselfexe, *hardcoded_fdpath; |
| |
| <------>E_asprintf(&procselfexe, "/proc/%d/exe", getpid()); |
| <------>rootfd = setup_testdir(); |
| |
| <------>hardcoded_fd = open("/dev/null", O_RDONLY); |
| <------>E_assert(hardcoded_fd >= 0, "open fd to hardcode"); |
| <------>E_asprintf(&hardcoded_fdpath, "self/fd/%d", hardcoded_fd); |
| |
| <------>struct basic_test tests[] = { |
| <------><------> |
| <------><------> |
| <------><------>{ .name = "[beneath] jump to /", |
| <------><------> .path = "/", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] absolute link to $root", |
| <------><------> .path = "cheeky/absself", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] chained absolute links to $root", |
| <------><------> .path = "abscheeky/absself", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] jump outside $root", |
| <------><------> .path = "..", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] temporary jump outside $root", |
| <------><------> .path = "../root/", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] symlink temporary jump outside $root", |
| <------><------> .path = "cheeky/self", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] chained symlink temporary jump outside $root", |
| <------><------> .path = "abscheeky/self", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] garbage links to $root", |
| <------><------> .path = "cheeky/garbageself", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] chained garbage links to $root", |
| <------><------> .path = "abscheeky/garbageself", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[beneath] ordinary path to 'root'", |
| <------><------> .path = "root", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[beneath] ordinary path to 'etc'", |
| <------><------> .path = "etc", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.path = "etc", .pass = true }, |
| <------><------>{ .name = "[beneath] ordinary path to 'etc/passwd'", |
| <------><------> .path = "etc/passwd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[beneath] relative symlink inside $root", |
| <------><------> .path = "relsym", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[beneath] chained-'..' relative symlink inside $root", |
| <------><------> .path = "cheeky/passwd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[beneath] absolute symlink component outside $root", |
| <------><------> .path = "abscheeky/passwd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] absolute symlink target outside $root", |
| <------><------> .path = "abssym", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] absolute path outside $root", |
| <------><------> .path = "/etc/passwd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] cheeky absolute path outside $root", |
| <------><------> .path = "cheeky/abspasswd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] chained cheeky absolute path outside $root", |
| <------><------> .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[beneath] tricky '..'-chained symlink outside $root", |
| <------><------> .path = "cheeky/dotdotlink", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] tricky absolute + '..'-chained symlink outside $root", |
| <------><------> .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] tricky garbage link outside $root", |
| <------><------> .path = "cheeky/garbagelink", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[beneath] tricky absolute + garbage link outside $root", |
| <------><------> .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_BENEATH, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| |
| <------><------> |
| <------><------> |
| <------><------>{ .name = "[in_root] jump to /", |
| <------><------> .path = "/", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = NULL, .pass = true }, |
| <------><------>{ .name = "[in_root] absolute symlink to /root", |
| <------><------> .path = "cheeky/absself", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = NULL, .pass = true }, |
| <------><------>{ .name = "[in_root] chained absolute symlinks to /root", |
| <------><------> .path = "abscheeky/absself", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = NULL, .pass = true }, |
| <------><------>{ .name = "[in_root] '..' at root", |
| <------><------> .path = "..", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = NULL, .pass = true }, |
| <------><------>{ .name = "[in_root] '../root' at root", |
| <------><------> .path = "../root/", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[in_root] relative symlink containing '..' above root", |
| <------><------> .path = "cheeky/self", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[in_root] garbage link to /root", |
| <------><------> .path = "cheeky/garbageself", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[in_root] chained garbage links to /root", |
| <------><------> .path = "abscheeky/garbageself", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[in_root] relative path to 'root'", |
| <------><------> .path = "root", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[in_root] relative path to 'etc'", |
| <------><------> .path = "etc", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc", .pass = true }, |
| <------><------>{ .name = "[in_root] relative path to 'etc/passwd'", |
| <------><------> .path = "etc/passwd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] relative symlink to 'etc/passwd'", |
| <------><------> .path = "relsym", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] chained-'..' relative symlink to 'etc/passwd'", |
| <------><------> .path = "cheeky/passwd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] chained-'..' absolute + relative symlink to 'etc/passwd'", |
| <------><------> .path = "abscheeky/passwd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] absolute symlink to 'etc/passwd'", |
| <------><------> .path = "abssym", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] absolute path 'etc/passwd'", |
| <------><------> .path = "/etc/passwd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] cheeky absolute path 'etc/passwd'", |
| <------><------> .path = "cheeky/abspasswd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] chained cheeky absolute path 'etc/passwd'", |
| <------><------> .path = "abscheeky/abspasswd", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky '..'-chained symlink outside $root", |
| <------><------> .path = "cheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky absolute + '..'-chained symlink outside $root", |
| <------><------> .path = "abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky absolute path + absolute + '..'-chained symlink outside $root", |
| <------><------> .path = "/../../../../abscheeky/dotdotlink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky garbage link outside $root", |
| <------><------> .path = "cheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky absolute + garbage link outside $root", |
| <------><------> .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------>{ .name = "[in_root] tricky absolute path + absolute + garbage link outside $root", |
| <------><------> .path = "/../../../../abscheeky/garbagelink", .how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------> |
| <------><------>{ .name = "[in_root] O_CREAT of relative path inside $root", |
| <------><------> .path = "newfile1", .how.flags = O_CREAT, |
| <------><------><------><------><------><------>.how.mode = 0700, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "newfile1", .pass = true }, |
| <------><------>{ .name = "[in_root] O_CREAT of absolute path", |
| <------><------> .path = "/newfile2", .how.flags = O_CREAT, |
| <------><------><------><------><------><------>.how.mode = 0700, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "newfile2", .pass = true }, |
| <------><------>{ .name = "[in_root] O_CREAT of tricky symlink outside root", |
| <------><------> .path = "/creatlink", .how.flags = O_CREAT, |
| <------><------><------><------><------><------>.how.mode = 0700, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_IN_ROOT, |
| <------><------> .out.path = "newfile3", .pass = true }, |
| |
| <------><------> |
| <------><------> |
| <------><------>{ .name = "[no_xdev] cross into $mnt", |
| <------><------> .path = "mnt", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] cross into $mnt/", |
| <------><------> .path = "mnt/", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] cross into $mnt/.", |
| <------><------> .path = "mnt/.", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[no_xdev] goto mountpoint root", |
| <------><------> .dir = "mnt", .path = ".", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.path = "mnt", .pass = true }, |
| <------><------>{ .name = "[no_xdev] cross up through '..'", |
| <------><------> .dir = "mnt", .path = "..", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] temporary cross up through '..'", |
| <------><------> .dir = "mnt", .path = "../mnt", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] temporary relative symlink cross up", |
| <------><------> .dir = "mnt", .path = "self", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] temporary absolute symlink cross up", |
| <------><------> .dir = "mnt", .path = "absself", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[no_xdev] jump to / directly", |
| <------><------> .dir = "mnt", .path = "/", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.path = "/", .pass = true }, |
| <------><------>{ .name = "[no_xdev] jump to / (from /) directly", |
| <------><------> .dir = "/", .path = "/", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.path = "/", .pass = true }, |
| <------><------>{ .name = "[no_xdev] jump to / then proc", |
| <------><------> .path = "/proc/1", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] jump to / then tmp", |
| <------><------> .path = "/tmp", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[no_xdev] cross through magic-link to self/root", |
| <------><------> .dir = "/proc", .path = "self/root", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------>{ .name = "[no_xdev] cross through magic-link to self/cwd", |
| <------><------> .dir = "/proc", .path = "self/cwd", .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.err = -EXDEV, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[no_xdev] jump through magic-link to same procfs", |
| <------><------> .dir = "/proc", .path = hardcoded_fdpath, .how.resolve = RESOLVE_NO_XDEV, |
| <------><------> .out.path = "/proc", .pass = true, }, |
| |
| <------><------> |
| <------><------> |
| <------><------>{ .name = "[no_magiclinks] ordinary relative symlink", |
| <------><------> .path = "relsym", .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------> |
| <------><------>{ .name = "[no_magiclinks] symlink to magic-link", |
| <------><------> .path = "procexe", .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_magiclinks] normal path to magic-link", |
| <------><------> .path = "/proc/self/exe", .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_magiclinks] normal path to magic-link with O_NOFOLLOW", |
| <------><------> .path = "/proc/self/exe", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.path = procselfexe, .pass = true }, |
| <------><------>{ .name = "[no_magiclinks] symlink to magic-link path component", |
| <------><------> .path = "procroot/etc", .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_magiclinks] magic-link path component", |
| <------><------> .path = "/proc/self/root/etc", .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_magiclinks] magic-link path component with O_NOFOLLOW", |
| <------><------> .path = "/proc/self/root/etc", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------> .how.resolve = RESOLVE_NO_MAGICLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| |
| <------><------> |
| <------><------> |
| <------><------>{ .name = "[no_symlinks] ordinary path to '.'", |
| <------><------> .path = ".", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = NULL, .pass = true }, |
| <------><------>{ .name = "[no_symlinks] ordinary path to 'root'", |
| <------><------> .path = "root", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "root", .pass = true }, |
| <------><------>{ .name = "[no_symlinks] ordinary path to 'etc'", |
| <------><------> .path = "etc", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "etc", .pass = true }, |
| <------><------>{ .name = "[no_symlinks] ordinary path to 'etc/passwd'", |
| <------><------> .path = "etc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "etc/passwd", .pass = true }, |
| <------><------> |
| <------><------>{ .name = "[no_symlinks] relative symlink target", |
| <------><------> .path = "relsym", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] relative symlink component", |
| <------><------> .path = "reletc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] absolute symlink target", |
| <------><------> .path = "abssym", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] absolute symlink component", |
| <------><------> .path = "absetc/passwd", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] cheeky garbage link", |
| <------><------> .path = "cheeky/garbagelink", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] cheeky absolute + garbage link", |
| <------><------> .path = "abscheeky/garbagelink", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] cheeky absolute + absolute symlink", |
| <------><------> .path = "abscheeky/absself", .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------> |
| <------><------>{ .name = "[no_symlinks] relative symlink with O_NOFOLLOW", |
| <------><------> .path = "relsym", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "relsym", .pass = true }, |
| <------><------>{ .name = "[no_symlinks] absolute symlink with O_NOFOLLOW", |
| <------><------> .path = "abssym", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "abssym", .pass = true }, |
| <------><------>{ .name = "[no_symlinks] trailing symlink with O_NOFOLLOW", |
| <------><------> .path = "cheeky/garbagelink", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.path = "cheeky/garbagelink", .pass = true }, |
| <------><------>{ .name = "[no_symlinks] multiple symlink components with O_NOFOLLOW", |
| <------><------> .path = "abscheeky/absself", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------>.how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------><------>{ .name = "[no_symlinks] multiple symlink (and garbage link) components with O_NOFOLLOW", |
| <------><------> .path = "abscheeky/garbagelink", .how.flags = O_NOFOLLOW, |
| <------><------><------><------><------><------> .how.resolve = RESOLVE_NO_SYMLINKS, |
| <------><------> .out.err = -ELOOP, .pass = false }, |
| <------>}; |
| |
| <------>BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_OPATH_TESTS); |
| |
| <------>for (int i = 0; i < ARRAY_LEN(tests); i++) { |
| <------><------>int dfd, fd; |
| <------><------>char *fdpath = NULL; |
| <------><------>bool failed; |
| <------><------>void (*resultfn)(const char *msg, ...) = ksft_test_result_pass; |
| <------><------>struct basic_test *test = &tests[i]; |
| |
| <------><------>if (!openat2_supported) { |
| <------><------><------>ksft_print_msg("openat2(2) unsupported\n"); |
| <------><------><------>resultfn = ksft_test_result_skip; |
| <------><------><------>goto skip; |
| <------><------>} |
| |
| <------><------> |
| <------><------>if (!(test->how.flags & O_CREAT)) |
| <------><------><------>test->how.flags |= O_PATH; |
| |
| <------><------>if (test->dir) |
| <------><------><------>dfd = openat(rootfd, test->dir, O_PATH | O_DIRECTORY); |
| <------><------>else |
| <------><------><------>dfd = dup(rootfd); |
| <------><------>E_assert(dfd, "failed to openat root '%s': %m", test->dir); |
| |
| <------><------>E_dup2(dfd, hardcoded_fd); |
| |
| <------><------>fd = sys_openat2(dfd, test->path, &test->how); |
| <------><------>if (test->pass) |
| <------><------><------>failed = (fd < 0 || !fdequal(fd, rootfd, test->out.path)); |
| <------><------>else |
| <------><------><------>failed = (fd != test->out.err); |
| <------><------>if (fd >= 0) { |
| <------><------><------>fdpath = fdreadlink(fd); |
| <------><------><------>close(fd); |
| <------><------>} |
| <------><------>close(dfd); |
| |
| <------><------>if (failed) { |
| <------><------><------>resultfn = ksft_test_result_fail; |
| |
| <------><------><------>ksft_print_msg("openat2 unexpectedly returned "); |
| <------><------><------>if (fdpath) |
| <------><------><------><------>ksft_print_msg("%d['%s']\n", fd, fdpath); |
| <------><------><------>else |
| <------><------><------><------>ksft_print_msg("%d (%s)\n", fd, strerror(-fd)); |
| <------><------>} |
| |
| skip: |
| <------><------>if (test->pass) |
| <------><------><------>resultfn("%s gives path '%s'\n", test->name, |
| <------><------><------><------> test->out.path ?: "."); |
| <------><------>else |
| <------><------><------>resultfn("%s fails with %d (%s)\n", test->name, |
| <------><------><------><------> test->out.err, strerror(-test->out.err)); |
| |
| <------><------>fflush(stdout); |
| <------><------>free(fdpath); |
| <------>} |
| |
| <------>free(procselfexe); |
| <------>close(rootfd); |
| |
| <------>free(hardcoded_fdpath); |
| <------>close(hardcoded_fd); |
| } |
| |
| #define NUM_TESTS NUM_OPENAT2_OPATH_TESTS |
| |
| int main(int argc, char **argv) |
| { |
| <------>ksft_print_header(); |
| <------>ksft_set_plan(NUM_TESTS); |
| |
| <------> |
| <------>if (geteuid() != 0) |
| <------><------>ksft_exit_skip("all tests require euid == 0\n"); |
| |
| <------>test_openat2_opath_tests(); |
| |
| <------>if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) |
| <------><------>ksft_exit_fail(); |
| <------>else |
| <------><------>ksft_exit_pass(); |
| } |
| |