--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author: Aleksa Sarai <cyphar@cyphar.com>
+ * Copyright (C) 2018-2019 SUSE LLC.
+ */
+
+#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"
+
+/*
+ * O_LARGEFILE is set to 0 by glibc.
+ * XXX: This is wrong on {mips, parisc, powerpc, sparc}.
+ */
+#undef O_LARGEFILE
+#define        O_LARGEFILE 0x8000
+
+struct open_how_ext {
+       struct open_how inner;
+       uint32_t extra1;
+       char pad1[128];
+       uint32_t extra2;
+       char pad2[128];
+       uint32_t extra3;
+};
+
+struct struct_test {
+       const char *name;
+       struct open_how_ext arg;
+       size_t size;
+       int err;
+};
+
+#define NUM_OPENAT2_STRUCT_TESTS 7
+#define NUM_OPENAT2_STRUCT_VARIATIONS 13
+
+void test_openat2_struct(void)
+{
+       int misalignments[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 17, 87 };
+
+       struct struct_test tests[] = {
+               /* Normal struct. */
+               { .name = "normal struct",
+                 .arg.inner.flags = O_RDONLY,
+                 .size = sizeof(struct open_how) },
+               /* Bigger struct, with zeroed out end. */
+               { .name = "bigger struct (zeroed out)",
+                 .arg.inner.flags = O_RDONLY,
+                 .size = sizeof(struct open_how_ext) },
+
+               /* TODO: Once expanded, check zero-padding. */
+
+               /* Smaller than version-0 struct. */
+               { .name = "zero-sized 'struct'",
+                 .arg.inner.flags = O_RDONLY, .size = 0, .err = -EINVAL },
+               { .name = "smaller-than-v0 struct",
+                 .arg.inner.flags = O_RDONLY,
+                 .size = OPEN_HOW_SIZE_VER0 - 1, .err = -EINVAL },
+
+               /* Bigger struct, with non-zero trailing bytes. */
+               { .name = "bigger struct (non-zero data in first 'future field')",
+                 .arg.inner.flags = O_RDONLY, .arg.extra1 = 0xdeadbeef,
+                 .size = sizeof(struct open_how_ext), .err = -E2BIG },
+               { .name = "bigger struct (non-zero data in middle of 'future fields')",
+                 .arg.inner.flags = O_RDONLY, .arg.extra2 = 0xfeedcafe,
+                 .size = sizeof(struct open_how_ext), .err = -E2BIG },
+               { .name = "bigger struct (non-zero data at end of 'future fields')",
+                 .arg.inner.flags = O_RDONLY, .arg.extra3 = 0xabad1dea,
+                 .size = sizeof(struct open_how_ext), .err = -E2BIG },
+       };
+
+       BUILD_BUG_ON(ARRAY_LEN(misalignments) != NUM_OPENAT2_STRUCT_VARIATIONS);
+       BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_STRUCT_TESTS);
+
+       for (int i = 0; i < ARRAY_LEN(tests); i++) {
+               struct struct_test *test = &tests[i];
+               struct open_how_ext how_ext = test->arg;
+
+               for (int j = 0; j < ARRAY_LEN(misalignments); j++) {
+                       int fd, misalign = misalignments[j];
+                       char *fdpath = NULL;
+                       bool failed;
+                       void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
+
+                       void *copy = NULL, *how_copy = &how_ext;
+
+                       if (!openat2_supported) {
+                               ksft_print_msg("openat2(2) unsupported\n");
+                               resultfn = ksft_test_result_skip;
+                               goto skip;
+                       }
+
+                       if (misalign) {
+                               /*
+                                * Explicitly misalign the structure copying it with the given
+                                * (mis)alignment offset. The other data is set to be non-zero to
+                                * make sure that non-zero bytes outside the struct aren't checked
+                                *
+                                * This is effectively to check that is_zeroed_user() works.
+                                */
+                               copy = malloc(misalign + sizeof(how_ext));
+                               how_copy = copy + misalign;
+                               memset(copy, 0xff, misalign);
+                               memcpy(how_copy, &how_ext, sizeof(how_ext));
+                       }
+
+                       fd = raw_openat2(AT_FDCWD, ".", how_copy, test->size);
+                       if (test->err >= 0)
+                               failed = (fd < 0);
+                       else
+                               failed = (fd != test->err);
+                       if (fd >= 0) {
+                               fdpath = fdreadlink(fd);
+                               close(fd);
+                       }
+
+                       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->err >= 0)
+                               resultfn("openat2 with %s argument [misalign=%d] succeeds\n",
+                                        test->name, misalign);
+                       else
+                               resultfn("openat2 with %s argument [misalign=%d] fails with %d (%s)\n",
+                                        test->name, misalign, test->err,
+                                        strerror(-test->err));
+
+                       free(copy);
+                       free(fdpath);
+                       fflush(stdout);
+               }
+       }
+}
+
+struct flag_test {
+       const char *name;
+       struct open_how how;
+       int err;
+};
+
+#define NUM_OPENAT2_FLAG_TESTS 23
+
+void test_openat2_flags(void)
+{
+       struct flag_test tests[] = {
+               /* O_TMPFILE is incompatible with O_PATH and O_CREAT. */
+               { .name = "incompatible flags (O_TMPFILE | O_PATH)",
+                 .how.flags = O_TMPFILE | O_PATH | O_RDWR, .err = -EINVAL },
+               { .name = "incompatible flags (O_TMPFILE | O_CREAT)",
+                 .how.flags = O_TMPFILE | O_CREAT | O_RDWR, .err = -EINVAL },
+
+               /* O_PATH only permits certain other flags to be set ... */
+               { .name = "compatible flags (O_PATH | O_CLOEXEC)",
+                 .how.flags = O_PATH | O_CLOEXEC },
+               { .name = "compatible flags (O_PATH | O_DIRECTORY)",
+                 .how.flags = O_PATH | O_DIRECTORY },
+               { .name = "compatible flags (O_PATH | O_NOFOLLOW)",
+                 .how.flags = O_PATH | O_NOFOLLOW },
+               /* ... and others are absolutely not permitted. */
+               { .name = "incompatible flags (O_PATH | O_RDWR)",
+                 .how.flags = O_PATH | O_RDWR, .err = -EINVAL },
+               { .name = "incompatible flags (O_PATH | O_CREAT)",
+                 .how.flags = O_PATH | O_CREAT, .err = -EINVAL },
+               { .name = "incompatible flags (O_PATH | O_EXCL)",
+                 .how.flags = O_PATH | O_EXCL, .err = -EINVAL },
+               { .name = "incompatible flags (O_PATH | O_NOCTTY)",
+                 .how.flags = O_PATH | O_NOCTTY, .err = -EINVAL },
+               { .name = "incompatible flags (O_PATH | O_DIRECT)",
+                 .how.flags = O_PATH | O_DIRECT, .err = -EINVAL },
+               { .name = "incompatible flags (O_PATH | O_LARGEFILE)",
+                 .how.flags = O_PATH | O_LARGEFILE, .err = -EINVAL },
+
+               /* ->mode must only be set with O_{CREAT,TMPFILE}. */
+               { .name = "non-zero how.mode and O_RDONLY",
+                 .how.flags = O_RDONLY, .how.mode = 0600, .err = -EINVAL },
+               { .name = "non-zero how.mode and O_PATH",
+                 .how.flags = O_PATH,   .how.mode = 0600, .err = -EINVAL },
+               { .name = "valid how.mode and O_CREAT",
+                 .how.flags = O_CREAT,  .how.mode = 0600 },
+               { .name = "valid how.mode and O_TMPFILE",
+                 .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0600 },
+               /* ->mode must only contain 0777 bits. */
+               { .name = "invalid how.mode and O_CREAT",
+                 .how.flags = O_CREAT,
+                 .how.mode = 0xFFFF, .err = -EINVAL },
+               { .name = "invalid (very large) how.mode and O_CREAT",
+                 .how.flags = O_CREAT,
+                 .how.mode = 0xC000000000000000ULL, .err = -EINVAL },
+               { .name = "invalid how.mode and O_TMPFILE",
+                 .how.flags = O_TMPFILE | O_RDWR,
+                 .how.mode = 0x1337, .err = -EINVAL },
+               { .name = "invalid (very large) how.mode and O_TMPFILE",
+                 .how.flags = O_TMPFILE | O_RDWR,
+                 .how.mode = 0x0000A00000000000ULL, .err = -EINVAL },
+
+               /* ->resolve must only contain RESOLVE_* flags. */
+               { .name = "invalid how.resolve and O_RDONLY",
+                 .how.flags = O_RDONLY,
+                 .how.resolve = 0x1337, .err = -EINVAL },
+               { .name = "invalid how.resolve and O_CREAT",
+                 .how.flags = O_CREAT,
+                 .how.resolve = 0x1337, .err = -EINVAL },
+               { .name = "invalid how.resolve and O_TMPFILE",
+                 .how.flags = O_TMPFILE | O_RDWR,
+                 .how.resolve = 0x1337, .err = -EINVAL },
+               { .name = "invalid how.resolve and O_PATH",
+                 .how.flags = O_PATH,
+                 .how.resolve = 0x1337, .err = -EINVAL },
+       };
+
+       BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_FLAG_TESTS);
+
+       for (int i = 0; i < ARRAY_LEN(tests); i++) {
+               int fd, fdflags = -1;
+               char *path, *fdpath = NULL;
+               bool failed = false;
+               struct flag_test *test = &tests[i];
+               void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
+
+               if (!openat2_supported) {
+                       ksft_print_msg("openat2(2) unsupported\n");
+                       resultfn = ksft_test_result_skip;
+                       goto skip;
+               }
+
+               path = (test->how.flags & O_CREAT) ? "/tmp/ksft.openat2_tmpfile" : ".";
+               unlink(path);
+
+               fd = sys_openat2(AT_FDCWD, path, &test->how);
+               if (test->err >= 0)
+                       failed = (fd < 0);
+               else
+                       failed = (fd != test->err);
+               if (fd >= 0) {
+                       int otherflags;
+
+                       fdpath = fdreadlink(fd);
+                       fdflags = fcntl(fd, F_GETFL);
+                       otherflags = fcntl(fd, F_GETFD);
+                       close(fd);
+
+                       E_assert(fdflags >= 0, "fcntl F_GETFL of new fd");
+                       E_assert(otherflags >= 0, "fcntl F_GETFD of new fd");
+
+                       /* O_CLOEXEC isn't shown in F_GETFL. */
+                       if (otherflags & FD_CLOEXEC)
+                               fdflags |= O_CLOEXEC;
+                       /* O_CREAT is hidden from F_GETFL. */
+                       if (test->how.flags & O_CREAT)
+                               fdflags |= O_CREAT;
+                       if (!(test->how.flags & O_LARGEFILE))
+                               fdflags &= ~O_LARGEFILE;
+                       failed |= (fdflags != test->how.flags);
+               }
+
+               if (failed) {
+                       resultfn = ksft_test_result_fail;
+
+                       ksft_print_msg("openat2 unexpectedly returned ");
+                       if (fdpath)
+                               ksft_print_msg("%d['%s'] with %X (!= %X)\n",
+                                              fd, fdpath, fdflags,
+                                              test->how.flags);
+                       else
+                               ksft_print_msg("%d (%s)\n", fd, strerror(-fd));
+               }
+
+skip:
+               if (test->err >= 0)
+                       resultfn("openat2 with %s succeeds\n", test->name);
+               else
+                       resultfn("openat2 with %s fails with %d (%s)\n",
+                                test->name, test->err, strerror(-test->err));
+
+               free(fdpath);
+               fflush(stdout);
+       }
+}
+
+#define NUM_TESTS (NUM_OPENAT2_STRUCT_VARIATIONS * NUM_OPENAT2_STRUCT_TESTS + \
+                  NUM_OPENAT2_FLAG_TESTS)
+
+int main(int argc, char **argv)
+{
+       ksft_print_header();
+       ksft_set_plan(NUM_TESTS);
+
+       test_openat2_struct();
+       test_openat2_flags();
+
+       if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
+               ksft_exit_fail();
+       else
+               ksft_exit_pass();
+}
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author: Aleksa Sarai <cyphar@cyphar.com>
+ * Copyright (C) 2018-2019 SUSE LLC.
+ */
+
+#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"
+
+/*
+ * Construct a test directory with the following structure:
+ *
+ * root/
+ * |-- procexe -> /proc/self/exe
+ * |-- procroot -> /proc/self/root
+ * |-- root/
+ * |-- mnt/ [mountpoint]
+ * |   |-- self -> ../mnt/
+ * |   `-- absself -> /mnt/
+ * |-- etc/
+ * |   `-- passwd
+ * |-- creatlink -> /newfile3
+ * |-- reletc -> etc/
+ * |-- relsym -> etc/passwd
+ * |-- absetc -> /etc/
+ * |-- abssym -> /etc/passwd
+ * |-- abscheeky -> /cheeky
+ * `-- cheeky/
+ *     |-- absself -> /
+ *     |-- self -> ../../root/
+ *     |-- garbageself -> /../../root/
+ *     |-- passwd -> ../cheeky/../cheeky/../etc/../etc/passwd
+ *     |-- abspasswd -> /../cheeky/../cheeky/../etc/../etc/passwd
+ *     |-- dotdotlink -> ../../../../../../../../../../../../../../etc/passwd
+ *     `-- garbagelink -> /../../../../../../../../../../../../../../etc/passwd
+ */
+int setup_testdir(void)
+{
+       int dfd, tmpfd;
+       char dirname[] = "/tmp/ksft-openat2-testdir.XXXXXX";
+
+       /* Unshare and make /tmp a new directory. */
+       E_unshare(CLONE_NEWNS);
+       E_mount("", "/tmp", "", MS_PRIVATE, "");
+
+       /* Make the top-level directory. */
+       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");
+
+       /* A sub-directory which is actually used for tests. */
+       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);
+
+       /* There is no mountat(2), so use chdir. */
+       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[] = {
+               /** RESOLVE_BENEATH **/
+               /* Attempts to cross dirfd should be blocked. */
+               { .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 },
+               /* Only relative paths that stay inside dirfd should work. */
+               { .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 },
+               /* Tricky paths should fail. */
+               { .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 },
+
+               /** RESOLVE_IN_ROOT **/
+               /* All attempts to cross the dirfd will be scoped-to-root. */
+               { .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] chainged 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 },
+               /* O_CREAT should handle trailing symlinks correctly. */
+               { .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 },
+
+               /** RESOLVE_NO_XDEV **/
+               /* Crossing *down* into a mountpoint is disallowed. */
+               { .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 },
+               /* Crossing *up* out of a mountpoint is disallowed. */
+               { .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 },
+               /* Jumping to "/" is ok, but later components cannot cross. */
+               { .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 },
+               /* Magic-links are blocked since they can switch vfsmounts. */
+               { .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 },
+               /* Except magic-link jumps inside the same vfsmount. */
+               { .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, },
+
+               /** RESOLVE_NO_MAGICLINKS **/
+               /* Regular symlinks should work. */
+               { .name = "[no_magiclinks] ordinary relative symlink",
+                 .path = "relsym",             .how.resolve = RESOLVE_NO_MAGICLINKS,
+                 .out.path = "etc/passwd",     .pass = true },
+               /* Magic-links should not work. */
+               { .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 },
+
+               /** RESOLVE_NO_SYMLINKS **/
+               /* Normal paths should work. */
+               { .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 },
+               /* Regular symlinks are blocked. */
+               { .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 },
+               /* Trailing symlinks with NO_FOLLOW. */
+               { .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;
+               }
+
+               /* Auto-set O_PATH. */
+               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);
+
+       /* NOTE: We should be checking for CAP_SYS_ADMIN here... */
+       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();
+}