]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
vfs: add O_EMPTYPATH to openat(2)/openat2(2)
authorJori Koolstra <jkoolstra@xs4all.nl>
Fri, 24 Apr 2026 11:46:02 +0000 (13:46 +0200)
committerChristian Brauner <brauner@kernel.org>
Thu, 21 May 2026 08:53:33 +0000 (10:53 +0200)
To get an operable version of an O_PATH file descriptor, it is possible
to use openat(fd, ".", O_DIRECTORY) for directories, but other files
currently require going through open("/proc/<pid>/fd/<nr>"), which
depends on a functioning procfs.

This patch adds the O_EMPTYPATH flag to openat(2)/openat2(2). If passed,
LOOKUP_EMPTY is set at path resolution time.

Note: This implies that you cannot rely anymore on disabling procfs from
being mounted (e.g. inside a container without procfs mounted and with
CAP_SYS_ADMIN dropped) to prevent O_PATH fds from being re-opened
read-write.

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Link: https://patch.msgid.link/20260424114611.1678641-2-jkoolstra@xs4all.nl
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/fcntl.c
fs/open.c
include/linux/fcntl.h
include/uapi/asm-generic/fcntl.h

index beab8080badf6eea2a9a684ff67e96eb672a8d8e..7d2165855a9c50128f76757664ed4780417d0aaf 100644 (file)
@@ -1169,7 +1169,7 @@ static int __init fcntl_init(void)
         * Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
         * is defined as O_NONBLOCK on some platforms and not on others.
         */
-       BUILD_BUG_ON(20 - 1 /* for O_RDONLY being 0 */ !=
+       BUILD_BUG_ON(21 - 1 /* for O_RDONLY being 0 */ !=
                HWEIGHT32(
                        (VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
                        __FMODE_EXEC));
index 681d405bc61ebe1162cc6389bbd9de1cd498d752..9e0164a8c1fbe688f85cc0deb525117a5343a2f6 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -1158,7 +1158,7 @@ struct file *kernel_file_open(const struct path *path, int flags,
 EXPORT_SYMBOL_GPL(kernel_file_open);
 
 #define WILL_CREATE(flags)     (flags & (O_CREAT | __O_TMPFILE))
-#define O_PATH_FLAGS           (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC)
+#define O_PATH_FLAGS           (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC | O_EMPTYPATH)
 
 inline struct open_how build_open_how(int flags, umode_t mode)
 {
@@ -1279,6 +1279,8 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
                lookup_flags |= LOOKUP_DIRECTORY;
        if (!(flags & O_NOFOLLOW))
                lookup_flags |= LOOKUP_FOLLOW;
+       if (flags & O_EMPTYPATH)
+               lookup_flags |= LOOKUP_EMPTY;
 
        if (how->resolve & RESOLVE_NO_XDEV)
                lookup_flags |= LOOKUP_NO_XDEV;
@@ -1360,7 +1362,7 @@ static int do_sys_openat2(int dfd, const char __user *filename,
        if (unlikely(err))
                return err;
 
-       CLASS(filename, name)(filename);
+       CLASS(filename_flags, name)(filename, op.lookup_flags);
        return FD_ADD(how->flags, do_file_open(dfd, name, &op));
 }
 
index a332e79b3207957d0b8b614679a151e986a07544..c65c5c73d362ff5d49dade5a589daa230bcd7143 100644 (file)
@@ -10,7 +10,7 @@
        (O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
         O_APPEND | O_NDELAY | O_NONBLOCK | __O_SYNC | O_DSYNC | \
         FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
-        O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE)
+        O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH)
 
 /* List of all valid flags for the how->resolve argument: */
 #define VALID_RESOLVE_FLAGS \
index 613475285643b3354c31b3b2567ccf8bccbd7148..bfc68156b45a33dd866160e5a75ba3b60308eaba 100644 (file)
 #define __O_TMPFILE    020000000
 #endif
 
+#ifndef O_EMPTYPATH
+#define O_EMPTYPATH    (1 << 26)       /* allow empty path */
+#endif
+
 /* a horrid kludge trying to make sure that this will fail on old kernels */
 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)