open: introduce openat2(2) syscall
authorAleksa Sarai <cyphar@cyphar.com>
Sat, 18 Jan 2020 12:07:59 +0000 (23:07 +1100)
committerAl Viro <viro@zeniv.linux.org.uk>
Sat, 18 Jan 2020 14:19:18 +0000 (09:19 -0500)
/* Background. */
For a very long time, extending openat(2) with new features has been
incredibly frustrating. This stems from the fact that openat(2) is
possibly the most famous counter-example to the mantra "don't silently
accept garbage from userspace" -- it doesn't check whether unknown flags
are present[1].

This means that (generally) the addition of new flags to openat(2) has
been fraught with backwards-compatibility issues (O_TMPFILE has to be
defined as __O_TMPFILE|O_DIRECTORY|[O_RDWR or O_WRONLY] to ensure old
kernels gave errors, since it's insecure to silently ignore the
flag[2]). All new security-related flags therefore have a tough road to
being added to openat(2).

Userspace also has a hard time figuring out whether a particular flag is
supported on a particular kernel. While it is now possible with
contemporary kernels (thanks to [3]), older kernels will expose unknown
flag bits through fcntl(F_GETFL). Giving a clear -EINVAL during
openat(2) time matches modern syscall designs and is far more
fool-proof.

In addition, the newly-added path resolution restriction LOOKUP flags
(which we would like to expose to user-space) don't feel related to the
pre-existing O_* flag set -- they affect all components of path lookup.
We'd therefore like to add a new flag argument.

Adding a new syscall allows us to finally fix the flag-ignoring problem,
and we can make it extensible enough so that we will hopefully never
need an openat3(2).

/* Syscall Prototype. */
  /*
   * open_how is an extensible structure (similar in interface to
   * clone3(2) or sched_setattr(2)). The size parameter must be set to
   * sizeof(struct open_how), to allow for future extensions. All future
   * extensions will be appended to open_how, with their zero value
   * acting as a no-op default.
   */
  struct open_how { /* ... */ };

  int openat2(int dfd, const char *pathname,
              struct open_how *how, size_t size);

/* Description. */
The initial version of 'struct open_how' contains the following fields:

  flags
    Used to specify openat(2)-style flags. However, any unknown flag
    bits or otherwise incorrect flag combinations (like O_PATH|O_RDWR)
    will result in -EINVAL. In addition, this field is 64-bits wide to
    allow for more O_ flags than currently permitted with openat(2).

  mode
    The file mode for O_CREAT or O_TMPFILE.

    Must be set to zero if flags does not contain O_CREAT or O_TMPFILE.

  resolve
    Restrict path resolution (in contrast to O_* flags they affect all
    path components). The current set of flags are as follows (at the
    moment, all of the RESOLVE_ flags are implemented as just passing
    the corresponding LOOKUP_ flag).

    RESOLVE_NO_XDEV       => LOOKUP_NO_XDEV
    RESOLVE_NO_SYMLINKS   => LOOKUP_NO_SYMLINKS
    RESOLVE_NO_MAGICLINKS => LOOKUP_NO_MAGICLINKS
    RESOLVE_BENEATH       => LOOKUP_BENEATH
    RESOLVE_IN_ROOT       => LOOKUP_IN_ROOT

open_how does not contain an embedded size field, because it is of
little benefit (userspace can figure out the kernel open_how size at
runtime fairly easily without it). It also only contains u64s (even
though ->mode arguably should be a u16) to avoid having padding fields
which are never used in the future.

Note that as a result of the new how->flags handling, O_PATH|O_TMPFILE
is no longer permitted for openat(2). As far as I can tell, this has
always been a bug and appears to not be used by userspace (and I've not
seen any problems on my machines by disallowing it). If it turns out
this breaks something, we can special-case it and only permit it for
openat(2) but not openat2(2).

After input from Florian Weimer, the new open_how and flag definitions
are inside a separate header from uapi/linux/fcntl.h, to avoid problems
that glibc has with importing that header.

/* Testing. */
In a follow-up patch there are over 200 selftests which ensure that this
syscall has the correct semantics and will correctly handle several
attack scenarios.

In addition, I've written a userspace library[4] which provides
convenient wrappers around openat2(RESOLVE_IN_ROOT) (this is necessary
because no other syscalls support RESOLVE_IN_ROOT, and thus lots of care
must be taken when using RESOLVE_IN_ROOT'd file descriptors with other
syscalls). During the development of this patch, I've run numerous
verification tests using libpathrs (showing that the API is reasonably
usable by userspace).

/* Future Work. */
Additional RESOLVE_ flags have been suggested during the review period.
These can be easily implemented separately (such as blocking auto-mount
during resolution).

Furthermore, there are some other proposed changes to the openat(2)
interface (the most obvious example is magic-link hardening[5]) which
would be a good opportunity to add a way for userspace to restrict how
O_PATH file descriptors can be re-opened.

Another possible avenue of future work would be some kind of
CHECK_FIELDS[6] flag which causes the kernel to indicate to userspace
which openat2(2) flags and fields are supported by the current kernel
(to avoid userspace having to go through several guesses to figure it
out).

[1]: https://lwn.net/Articles/588444/
[2]: https://lore.kernel.org/lkml/CA+55aFyyxJL1LyXZeBsf2ypriraj5ut1XkNDsunRBqgVjZU_6Q@mail.gmail.com
[3]: commit 629e014bb834 ("fs: completely ignore unknown open flags")
[4]: https://sourceware.org/bugzilla/show_bug.cgi?id=17523
[5]: https://lore.kernel.org/lkml/20190930183316.10190-2-cyphar@cyphar.com/
[6]: https://youtu.be/ggD-eb3yPVs

Suggested-by: Christian Brauner <christian.brauner@ubuntu.com>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
26 files changed:
CREDITS
MAINTAINERS
arch/alpha/kernel/syscalls/syscall.tbl
arch/arm/tools/syscall.tbl
arch/arm64/include/asm/unistd.h
arch/arm64/include/asm/unistd32.h
arch/ia64/kernel/syscalls/syscall.tbl
arch/m68k/kernel/syscalls/syscall.tbl
arch/microblaze/kernel/syscalls/syscall.tbl
arch/mips/kernel/syscalls/syscall_n32.tbl
arch/mips/kernel/syscalls/syscall_n64.tbl
arch/mips/kernel/syscalls/syscall_o32.tbl
arch/parisc/kernel/syscalls/syscall.tbl
arch/powerpc/kernel/syscalls/syscall.tbl
arch/s390/kernel/syscalls/syscall.tbl
arch/sh/kernel/syscalls/syscall.tbl
arch/sparc/kernel/syscalls/syscall.tbl
arch/x86/entry/syscalls/syscall_32.tbl
arch/x86/entry/syscalls/syscall_64.tbl
arch/xtensa/kernel/syscalls/syscall.tbl
fs/open.c
include/linux/fcntl.h
include/linux/syscalls.h
include/uapi/asm-generic/unistd.h
include/uapi/linux/fcntl.h
include/uapi/linux/openat2.h [new file with mode: 0644]

diff --git a/CREDITS b/CREDITS
index 9602b0fa1c958da4b605127c2b8da0628efeb34e..a97d3280a627b3665e68c94a574409f71cee1da6 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -3302,7 +3302,9 @@ S: France
 N: Aleksa Sarai
 E: cyphar@cyphar.com
 W: https://www.cyphar.com/
-D: `pids` cgroup subsystem
+D: /sys/fs/cgroup/pids
+D: openat2(2)
+S: Sydney, Australia
 
 N: Dipankar Sarma
 E: dipankar@in.ibm.com
index bd5847e802defb11887f45dd17412e1e98d054e8..737ada377ac3b4ffc17d524095b76760ffa467b6 100644 (file)
@@ -6397,6 +6397,7 @@ F:        fs/*
 F:     include/linux/fs.h
 F:     include/linux/fs_types.h
 F:     include/uapi/linux/fs.h
+F:     include/uapi/linux/openat2.h
 
 FINTEK F75375S HARDWARE MONITOR AND FAN CONTROLLER DRIVER
 M:     Riku Voipio <riku.voipio@iki.fi>
index 8e13b0b2928d603b9c0820ebdabac6cdbb2a170a..4d7f2ffa957c14c5422b12da725614d866d9970f 100644 (file)
 543    common  fspick                          sys_fspick
 544    common  pidfd_open                      sys_pidfd_open
 # 545 reserved for clone3
+547    common  openat2                         sys_openat2
index 6da7dc4d79cc40180886800a74ca4d475398be0f..4ba54bc7e19a6795d417582ae5e3ed33e3c17e28 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 435    common  clone3                          sys_clone3
+437    common  openat2                         sys_openat2
index 2629a68b87244facd78a6ad5e9d4b18ca6ac18ad..8aa00ccb0b96484989b4c3897dfd296a8fd8d34e 100644 (file)
@@ -38,7 +38,7 @@
 #define __ARM_NR_compat_set_tls                (__ARM_NR_COMPAT_BASE + 5)
 #define __ARM_NR_COMPAT_END            (__ARM_NR_COMPAT_BASE + 0x800)
 
-#define __NR_compat_syscalls           436
+#define __NR_compat_syscalls           438
 #endif
 
 #define __ARCH_WANT_SYS_CLONE
index 94ab29cf4f00306d95d72f64a306445dbb062fb9..57f6f592d460c990a232cfa982bc440b732c912d 100644 (file)
@@ -879,6 +879,8 @@ __SYSCALL(__NR_fspick, sys_fspick)
 __SYSCALL(__NR_pidfd_open, sys_pidfd_open)
 #define __NR_clone3 435
 __SYSCALL(__NR_clone3, sys_clone3)
+#define __NR_openat2 437
+__SYSCALL(__NR_openat2, sys_openat2)
 
 /*
  * Please add new compat syscalls above this comment and update
index 36d5faf4c86cef846d5d4369811050ae472f097e..8d36f2e2dc892272eeac9caa963a7a72c7e6b3c5 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 # 435 reserved for clone3
+437    common  openat2                         sys_openat2
index a88a285a0e5f6c5ca724ad27c36b59b362f0b988..2559925f1924589da9888989a4032674b56c1c93 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 # 435 reserved for clone3
+437    common  openat2                         sys_openat2
index 09b0cd7dab0a6f009fa9b2d057791f1342e38330..c04385e60833d6226be5a3663fdf3d5a35545532 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 435    common  clone3                          sys_clone3
+437    common  openat2                         sys_openat2
index e7c5ab38e403c0dee1321134847d13c5855f32a8..68c9ec06851f8942fe0e848cbc531a2346fdc188 100644 (file)
 433    n32     fspick                          sys_fspick
 434    n32     pidfd_open                      sys_pidfd_open
 435    n32     clone3                          __sys_clone3
+437    n32     openat2                         sys_openat2
index 13cd66581f3bed758b312165c28a4a75dc812942..42a72d01005087db40910b2b359b6fdedce5867e 100644 (file)
 433    n64     fspick                          sys_fspick
 434    n64     pidfd_open                      sys_pidfd_open
 435    n64     clone3                          __sys_clone3
+437    n64     openat2                         sys_openat2
index 353539ea4140a410fc258650a08544da05f9cd23..f114c4aed0ed901fe3d4ac97f0a43d0040534268 100644 (file)
 433    o32     fspick                          sys_fspick
 434    o32     pidfd_open                      sys_pidfd_open
 435    o32     clone3                          __sys_clone3
+437    o32     openat2                         sys_openat2
index 285ff516150cf41670aede1f1abf94464230dd55..b550ae9a7fea9d55260a5ff1722d5df6abf09ef8 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 435    common  clone3                          sys_clone3_wrapper
+437    common  openat2                         sys_openat2
index 43f736ed47f28a1dff42ffb7ae7b3e19613e0211..a8b5ecb5b602c2d44c92c31d9b74c5746f5d2f98 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 435    nospu   clone3                          ppc_clone3
+437    common  openat2                         sys_openat2
index 3054e9c035a39fa051b33cd4655f91885ce534a3..16b571c06161d534841a0dedb09034e4abbe8585 100644 (file)
 433  common    fspick                  sys_fspick                      sys_fspick
 434  common    pidfd_open              sys_pidfd_open                  sys_pidfd_open
 435  common    clone3                  sys_clone3                      sys_clone3
+437  common    openat2                 sys_openat2                     sys_openat2
index b5ed26c4c005d616b3161d03d1cb53c793a6e51a..a7185cc1862657f1d1bfe045ce9f7ef19ba7c671 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 # 435 reserved for clone3
+437    common  openat2                         sys_openat2
index 8c8cc7537fb274eef7fce0f1489057298e14a310..b11c195520221947919ab63c66c990d0d695f5b4 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 # 435 reserved for clone3
+437    common  openat2                 sys_openat2
index 15908eb9b17e5eeda97e867b85da2f261f019503..d22a8b5c3fab97d6e29b3d78d362432e2edcf828 100644 (file)
 433    i386    fspick                  sys_fspick                      __ia32_sys_fspick
 434    i386    pidfd_open              sys_pidfd_open                  __ia32_sys_pidfd_open
 435    i386    clone3                  sys_clone3                      __ia32_sys_clone3
+437    i386    openat2                 sys_openat2                     __ia32_sys_openat2
index c29976eca4a8a86bdd2fc062b3e521afc0338301..9035647ef236d5355a223162698eba0b49b50c53 100644 (file)
 433    common  fspick                  __x64_sys_fspick
 434    common  pidfd_open              __x64_sys_pidfd_open
 435    common  clone3                  __x64_sys_clone3/ptregs
+437    common  openat2                 __x64_sys_openat2
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
index 25f4de729a6d03ed12e728a726bd28c661fd3fa2..f0a68013c0384c53c66284c320b7fc78728575c5 100644 (file)
 433    common  fspick                          sys_fspick
 434    common  pidfd_open                      sys_pidfd_open
 435    common  clone3                          sys_clone3
+437    common  openat2                         sys_openat2
index b62f5c0923a80cc168904b940e564fb2721a4f40..8cdb2b675867802eb3afe7877624c1bf3ea61b4c 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -955,48 +955,84 @@ struct file *open_with_fake_path(const struct path *path, int flags,
 }
 EXPORT_SYMBOL(open_with_fake_path);
 
-static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
+#define WILL_CREATE(flags)     (flags & (O_CREAT | __O_TMPFILE))
+#define O_PATH_FLAGS           (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC)
+
+static inline struct open_how build_open_how(int flags, umode_t mode)
+{
+       struct open_how how = {
+               .flags = flags & VALID_OPEN_FLAGS,
+               .mode = mode & S_IALLUGO,
+       };
+
+       /* O_PATH beats everything else. */
+       if (how.flags & O_PATH)
+               how.flags &= O_PATH_FLAGS;
+       /* Modes should only be set for create-like flags. */
+       if (!WILL_CREATE(how.flags))
+               how.mode = 0;
+       return how;
+}
+
+static inline int build_open_flags(const struct open_how *how,
+                                  struct open_flags *op)
 {
+       int flags = how->flags;
        int lookup_flags = 0;
        int acc_mode = ACC_MODE(flags);
 
+       /* Must never be set by userspace */
+       flags &= ~(FMODE_NONOTIFY | O_CLOEXEC);
+
        /*
-        * Clear out all open flags we don't know about so that we don't report
-        * them in fcntl(F_GETFD) or similar interfaces.
+        * Older syscalls implicitly clear all of the invalid flags or argument
+        * values before calling build_open_flags(), but openat2(2) checks all
+        * of its arguments.
         */
-       flags &= VALID_OPEN_FLAGS;
+       if (flags & ~VALID_OPEN_FLAGS)
+               return -EINVAL;
+       if (how->resolve & ~VALID_RESOLVE_FLAGS)
+               return -EINVAL;
 
-       if (flags & (O_CREAT | __O_TMPFILE))
-               op->mode = (mode & S_IALLUGO) | S_IFREG;
-       else
+       /* Deal with the mode. */
+       if (WILL_CREATE(flags)) {
+               if (how->mode & ~S_IALLUGO)
+                       return -EINVAL;
+               op->mode = how->mode | S_IFREG;
+       } else {
+               if (how->mode != 0)
+                       return -EINVAL;
                op->mode = 0;
-
-       /* Must never be set by userspace */
-       flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;
+       }
 
        /*
-        * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only
-        * check for O_DSYNC if the need any syncing at all we enforce it's
-        * always set instead of having to deal with possibly weird behaviour
-        * for malicious applications setting only __O_SYNC.
+        * In order to ensure programs get explicit errors when trying to use
+        * O_TMPFILE on old kernels, O_TMPFILE is implemented such that it
+        * looks like (O_DIRECTORY|O_RDWR & ~O_CREAT) to old kernels. But we
+        * have to require userspace to explicitly set it.
         */
-       if (flags & __O_SYNC)
-               flags |= O_DSYNC;
-
        if (flags & __O_TMPFILE) {
                if ((flags & O_TMPFILE_MASK) != O_TMPFILE)
                        return -EINVAL;
                if (!(acc_mode & MAY_WRITE))
                        return -EINVAL;
-       } else if (flags & O_PATH) {
-               /*
-                * If we have O_PATH in the open flag. Then we
-                * cannot have anything other than the below set of flags
-                */
-               flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
+       }
+       if (flags & O_PATH) {
+               /* O_PATH only permits certain other flags to be set. */
+               if (flags & ~O_PATH_FLAGS)
+                       return -EINVAL;
                acc_mode = 0;
        }
 
+       /*
+        * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only
+        * check for O_DSYNC if the need any syncing at all we enforce it's
+        * always set instead of having to deal with possibly weird behaviour
+        * for malicious applications setting only __O_SYNC.
+        */
+       if (flags & __O_SYNC)
+               flags |= O_DSYNC;
+
        op->open_flag = flags;
 
        /* O_TRUNC implies we need access checks for write permissions */
@@ -1022,6 +1058,18 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o
                lookup_flags |= LOOKUP_DIRECTORY;
        if (!(flags & O_NOFOLLOW))
                lookup_flags |= LOOKUP_FOLLOW;
+
+       if (how->resolve & RESOLVE_NO_XDEV)
+               lookup_flags |= LOOKUP_NO_XDEV;
+       if (how->resolve & RESOLVE_NO_MAGICLINKS)
+               lookup_flags |= LOOKUP_NO_MAGICLINKS;
+       if (how->resolve & RESOLVE_NO_SYMLINKS)
+               lookup_flags |= LOOKUP_NO_SYMLINKS;
+       if (how->resolve & RESOLVE_BENEATH)
+               lookup_flags |= LOOKUP_BENEATH;
+       if (how->resolve & RESOLVE_IN_ROOT)
+               lookup_flags |= LOOKUP_IN_ROOT;
+
        op->lookup_flags = lookup_flags;
        return 0;
 }
@@ -1040,8 +1088,11 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o
 struct file *file_open_name(struct filename *name, int flags, umode_t mode)
 {
        struct open_flags op;
-       int err = build_open_flags(flags, mode, &op);
-       return err ? ERR_PTR(err) : do_filp_open(AT_FDCWD, name, &op);
+       struct open_how how = build_open_how(flags, mode);
+       int err = build_open_flags(&how, &op);
+       if (err)
+               return ERR_PTR(err);
+       return do_filp_open(AT_FDCWD, name, &op);
 }
 
 /**
@@ -1072,17 +1123,19 @@ struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt,
                            const char *filename, int flags, umode_t mode)
 {
        struct open_flags op;
-       int err = build_open_flags(flags, mode, &op);
+       struct open_how how = build_open_how(flags, mode);
+       int err = build_open_flags(&how, &op);
        if (err)
                return ERR_PTR(err);
        return do_file_open_root(dentry, mnt, filename, &op);
 }
 EXPORT_SYMBOL(file_open_root);
 
-long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
+static long do_sys_openat2(int dfd, const char __user *filename,
+                          struct open_how *how)
 {
        struct open_flags op;
-       int fd = build_open_flags(flags, mode, &op);
+       int fd = build_open_flags(how, &op);
        struct filename *tmp;
 
        if (fd)
@@ -1092,7 +1145,7 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
        if (IS_ERR(tmp))
                return PTR_ERR(tmp);
 
-       fd = get_unused_fd_flags(flags);
+       fd = get_unused_fd_flags(how->flags);
        if (fd >= 0) {
                struct file *f = do_filp_open(dfd, tmp, &op);
                if (IS_ERR(f)) {
@@ -1107,12 +1160,16 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
        return fd;
 }
 
-SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
+long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
-       if (force_o_largefile())
-               flags |= O_LARGEFILE;
+       struct open_how how = build_open_how(flags, mode);
+       return do_sys_openat2(dfd, filename, &how);
+}
 
-       return do_sys_open(AT_FDCWD, filename, flags, mode);
+
+SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
+{
+       return ksys_open(filename, flags, mode);
 }
 
 SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,
@@ -1120,10 +1177,32 @@ SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,
 {
        if (force_o_largefile())
                flags |= O_LARGEFILE;
-
        return do_sys_open(dfd, filename, flags, mode);
 }
 
+SYSCALL_DEFINE4(openat2, int, dfd, const char __user *, filename,
+               struct open_how __user *, how, size_t, usize)
+{
+       int err;
+       struct open_how tmp;
+
+       BUILD_BUG_ON(sizeof(struct open_how) < OPEN_HOW_SIZE_VER0);
+       BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_LATEST);
+
+       if (unlikely(usize < OPEN_HOW_SIZE_VER0))
+               return -EINVAL;
+
+       err = copy_struct_from_user(&tmp, sizeof(tmp), how, usize);
+       if (err)
+               return err;
+
+       /* O_LARGEFILE is only allowed for non-O_PATH. */
+       if (!(tmp.flags & O_PATH) && force_o_largefile())
+               tmp.flags |= O_LARGEFILE;
+
+       return do_sys_openat2(dfd, filename, &tmp);
+}
+
 #ifdef CONFIG_COMPAT
 /*
  * Exactly like sys_open(), except that it doesn't set the
index d019df946cb24d261effd36d542a75b2ec00e243..7bcdcf4f6ab2f1b5ccfa3b9b1667174c4bfbf1b9 100644 (file)
@@ -2,15 +2,29 @@
 #ifndef _LINUX_FCNTL_H
 #define _LINUX_FCNTL_H
 
+#include <linux/stat.h>
 #include <uapi/linux/fcntl.h>
 
-/* list of all valid flags for the open/openat flags argument: */
+/* List of all valid flags for the open/openat flags argument: */
 #define VALID_OPEN_FLAGS \
        (O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
         O_APPEND | O_NDELAY | O_NONBLOCK | O_NDELAY | __O_SYNC | O_DSYNC | \
         FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
         O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE)
 
+/* List of all valid flags for the how->upgrade_mask argument: */
+#define VALID_UPGRADE_FLAGS \
+       (UPGRADE_NOWRITE | UPGRADE_NOREAD)
+
+/* List of all valid flags for the how->resolve argument: */
+#define VALID_RESOLVE_FLAGS \
+       (RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
+        RESOLVE_BENEATH | RESOLVE_IN_ROOT)
+
+/* List of all open_how "versions". */
+#define OPEN_HOW_SIZE_VER0     24 /* sizeof first published struct */
+#define OPEN_HOW_SIZE_LATEST   OPEN_HOW_SIZE_VER0
+
 #ifndef force_o_largefile
 #define force_o_largefile() (!IS_ENABLED(CONFIG_ARCH_32BIT_OFF_T))
 #endif
index d0391cc2dae90611d9ec91187342202300efc01c..cd9f27cbc56726dc9f588f5bb4b22018999b372e 100644 (file)
@@ -69,6 +69,7 @@ struct rseq;
 union bpf_attr;
 struct io_uring_params;
 struct clone_args;
+struct open_how;
 
 #include <linux/types.h>
 #include <linux/aio_abi.h>
@@ -439,6 +440,8 @@ asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user,
 asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group);
 asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,
                           umode_t mode);
+asmlinkage long sys_openat2(int dfd, const char __user *filename,
+                           struct open_how *how, size_t size);
 asmlinkage long sys_close(unsigned int fd);
 asmlinkage long sys_vhangup(void);
 
index 1fc8faa6e97306dfa95335ecba91b3777a843aa9..d4122c0914720931f125e552ce58083683133998 100644 (file)
@@ -851,8 +851,11 @@ __SYSCALL(__NR_pidfd_open, sys_pidfd_open)
 __SYSCALL(__NR_clone3, sys_clone3)
 #endif
 
+#define __NR_openat2 437
+__SYSCALL(__NR_openat2, sys_openat2)
+
 #undef __NR_syscalls
-#define __NR_syscalls 436
+#define __NR_syscalls 438
 
 /*
  * 32 bit systems traditionally used different
index 1f97b33c840e09936fb92b6a13d82e333d0e8151..ca88b7bce55385b41203284196923d09250f2ea3 100644 (file)
@@ -3,6 +3,7 @@
 #define _UAPI_LINUX_FCNTL_H
 
 #include <asm/fcntl.h>
+#include <linux/openat2.h>
 
 #define F_SETLEASE     (F_LINUX_SPECIFIC_BASE + 0)
 #define F_GETLEASE     (F_LINUX_SPECIFIC_BASE + 1)
 
 #define AT_RECURSIVE           0x8000  /* Apply to the entire subtree */
 
-
 #endif /* _UAPI_LINUX_FCNTL_H */
diff --git a/include/uapi/linux/openat2.h b/include/uapi/linux/openat2.h
new file mode 100644 (file)
index 0000000..58b1eb7
--- /dev/null
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_OPENAT2_H
+#define _UAPI_LINUX_OPENAT2_H
+
+#include <linux/types.h>
+
+/*
+ * Arguments for how openat2(2) should open the target path. If only @flags and
+ * @mode are non-zero, then openat2(2) operates very similarly to openat(2).
+ *
+ * However, unlike openat(2), unknown or invalid bits in @flags result in
+ * -EINVAL rather than being silently ignored. @mode must be zero unless one of
+ * {O_CREAT, O_TMPFILE} are set.
+ *
+ * @flags: O_* flags.
+ * @mode: O_CREAT/O_TMPFILE file mode.
+ * @resolve: RESOLVE_* flags.
+ */
+struct open_how {
+       __u64 flags;
+       __u64 mode;
+       __u64 resolve;
+};
+
+/* how->resolve flags for openat2(2). */
+#define RESOLVE_NO_XDEV                0x01 /* Block mount-point crossings
+                                       (includes bind-mounts). */
+#define RESOLVE_NO_MAGICLINKS  0x02 /* Block traversal through procfs-style
+                                       "magic-links". */
+#define RESOLVE_NO_SYMLINKS    0x04 /* Block traversal through all symlinks
+                                       (implies OEXT_NO_MAGICLINKS) */
+#define RESOLVE_BENEATH                0x08 /* Block "lexical" trickery like
+                                       "..", symlinks, and absolute
+                                       paths which escape the dirfd. */
+#define RESOLVE_IN_ROOT                0x10 /* Make all jumps to "/" and ".."
+                                       be scoped inside the dirfd
+                                       (similar to chroot(2)). */
+
+#endif /* _UAPI_LINUX_OPENAT2_H */