mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Merge patch series "vfs: add O_EMPTYPATH to openat(2)/openat2(2)"
Jori Koolstra <jkoolstra@xs4all.nl> says:
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.
* patches from https://patch.msgid.link/20260424114611.1678641-1-jkoolstra@xs4all.nl:
selftest: add tests for O_EMPTYPATH
vfs: add O_EMPTYPATH to openat(2)/openat2(2)
Link: https://patch.msgid.link/20260424114611.1678641-1-jkoolstra@xs4all.nl
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
commit
09e8b7a428
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@
|
|||
#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)
|
||||
|
||||
|
|
|
|||
43
tools/include/uapi/linux/openat2.h
Normal file
43
tools/include/uapi/linux/openat2.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef _LINUX_OPENAT2_H
|
||||
#define _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)). */
|
||||
#define RESOLVE_CACHED 0x20 /* Only complete if resolution can be
|
||||
completed through cached lookup. May
|
||||
return -EAGAIN if that's not
|
||||
possible. */
|
||||
|
||||
#endif /* _LINUX_OPENAT2_H */
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined
|
||||
TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test
|
||||
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined $(TOOLS_INCLUDES)
|
||||
TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test emptypath_test
|
||||
|
||||
# gcc requires -static-libasan in order to ensure that Address Sanitizer's
|
||||
# library is the first one loaded. However, clang already statically links the
|
||||
|
|
|
|||
55
tools/testing/selftests/filesystems/openat2/emptypath_test.c
Normal file
55
tools/testing/selftests/filesystems/openat2/emptypath_test.c
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "kselftest.h"
|
||||
|
||||
#ifndef O_EMPTYPATH
|
||||
#define O_EMPTYPATH (1 << 26)
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int opath_fd, reopen_fd;
|
||||
const char *path = "/tmp/emptypath_test";
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(2);
|
||||
|
||||
opath_fd = open(path, O_CREAT | O_WRONLY, S_IRWXU);
|
||||
if (opath_fd < 0)
|
||||
ksft_exit_fail_msg("create %s: %m\n", path);
|
||||
close(opath_fd);
|
||||
|
||||
opath_fd = open(path, O_PATH);
|
||||
if (opath_fd < 0)
|
||||
ksft_exit_fail_msg("open %s O_PATH: %m\n", path);
|
||||
|
||||
reopen_fd = openat(opath_fd, "", O_RDONLY);
|
||||
if (reopen_fd < 0 && errno == ENOENT)
|
||||
ksft_test_result_pass("empty path without O_EMPTYPATH returns ENOENT\n");
|
||||
else if (reopen_fd >= 0) {
|
||||
ksft_test_result_fail("empty path without O_EMPTYPATH unexpectedly succeeded\n");
|
||||
close(reopen_fd);
|
||||
} else {
|
||||
ksft_test_result_fail("empty path without O_EMPTYPATH: expected ENOENT, got %m\n");
|
||||
}
|
||||
|
||||
reopen_fd = openat(opath_fd, "", O_RDONLY | O_EMPTYPATH);
|
||||
|
||||
if (reopen_fd < 0 && errno == EINVAL)
|
||||
ksft_exit_skip("O_EMPTYPATH not supported\n");
|
||||
|
||||
if (reopen_fd >= 0) {
|
||||
ksft_test_result_pass("O_EMPTYPATH reopens O_PATH fd\n");
|
||||
close(reopen_fd);
|
||||
} else {
|
||||
ksft_test_result_fail("O_EMPTYPATH failed: %m\n");
|
||||
}
|
||||
|
||||
unlink(path);
|
||||
ksft_finished();
|
||||
}
|
||||
|
|
@ -15,47 +15,14 @@
|
|||
#include <limits.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/openat2.h>
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); })))
|
||||
|
||||
/*
|
||||
* Arguments for how openat2(2) should open the target path. If @resolve is
|
||||
* zero, then openat2(2) operates very similarly to openat(2).
|
||||
*
|
||||
* However, unlike openat(2), unknown 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;
|
||||
};
|
||||
|
||||
#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
|
||||
#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0
|
||||
|
||||
#ifndef RESOLVE_IN_ROOT
|
||||
/* 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 /* RESOLVE_IN_ROOT */
|
||||
|
||||
__maybe_unused
|
||||
static bool needs_openat2(const struct open_how *how)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user