mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
Merge patch series "OPENAT2_REGULAR flag support for openat2"
Dorjoy Chowdhury <dorjoychy111@gmail.com> says: I came upon this "Ability to only open regular files" uapi feature suggestion from https://uapi-group.org/kernel-features/#ability-to-only-open-regular-files and thought it would be something I could do as a first patch and get to know the kernel code a bit better. The following filesystems have been tested by building and booting the kernel x86 bzImage in a Fedora 43 VM in QEMU. I have tested with OPENAT2_REGULAR that regular files can be successfully opened and non-regular files (directory, fifo etc) return -EFTYPE. - btrfs - NFS (loopback) - SMB (loopback) Christian Brauner (Amutable) <brauner@kernel.org>: All atomic_open implementations were audited for OPENAT2_REGULAR handling. Explicit checks were added to ceph, gfs2, nfs (v4), and cifs/smb — these are the filesystems whose atomic_open can encounter an existing non-regular file and would otherwise call finish_open() on it or return a misleading error code. The checks allow these filesystems to return -EFTYPE directly and avoid unnecessary open+close round-trips. The remaining implementations (9p, fuse, vboxsf, nfs v2/v3) don't need explicit checks. They only call finish_open() on freshly created files (always S_IFREG) and use finish_no_open() for lookup hits, letting the VFS catch non-regular files via the do_open() safety net. Notably, fuse also validates the server response (S_ISREG check on the reply) before reaching finish_open(). * patches from https://patch.msgid.link/20260328172314.45807-1-dorjoychy111@gmail.com: kselftest/openat2: test for OPENAT2_REGULAR flag openat2: new OPENAT2_REGULAR flag support Link: https://patch.msgid.link/20260328172314.45807-1-dorjoychy111@gmail.com Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
commit
21688d8122
|
|
@ -127,4 +127,6 @@
|
|||
|
||||
#define EHWPOISON 139 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 140 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@
|
|||
|
||||
#define EHWPOISON 168 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 169 /* Wrong file type for the intended operation */
|
||||
|
||||
#define EDQUOT 1133 /* Quota exceeded */
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -124,4 +124,6 @@
|
|||
|
||||
#define EHWPOISON 257 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 258 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -117,4 +117,6 @@
|
|||
|
||||
#define EHWPOISON 135 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 136 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -996,6 +996,10 @@ int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
|
|||
ceph_init_inode_acls(newino, &as_ctx);
|
||||
file->f_mode |= FMODE_CREATED;
|
||||
}
|
||||
if ((flags & __O_REGULAR) && !d_is_reg(dentry)) {
|
||||
err = -EFTYPE;
|
||||
goto out_req;
|
||||
}
|
||||
err = finish_open(file, dentry, ceph_open);
|
||||
}
|
||||
out_req:
|
||||
|
|
|
|||
|
|
@ -1169,10 +1169,10 @@ 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(21 - 1 /* for O_RDONLY being 0 */ !=
|
||||
BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ !=
|
||||
HWEIGHT32(
|
||||
(VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
|
||||
__FMODE_EXEC));
|
||||
__FMODE_EXEC | __O_REGULAR));
|
||||
|
||||
fasync_cache = kmem_cache_create("fasync_cache",
|
||||
sizeof(struct fasync_struct), 0,
|
||||
|
|
|
|||
|
|
@ -738,6 +738,13 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry,
|
|||
inode = gfs2_dir_search(dir, &dentry->d_name, !S_ISREG(mode) || excl);
|
||||
error = PTR_ERR(inode);
|
||||
if (!IS_ERR(inode)) {
|
||||
if (file && (file->f_flags & __O_REGULAR) &&
|
||||
!S_ISREG(inode->i_mode)) {
|
||||
iput(inode);
|
||||
inode = NULL;
|
||||
error = -EFTYPE;
|
||||
goto fail_gunlock;
|
||||
}
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
iput(inode);
|
||||
inode = NULL;
|
||||
|
|
|
|||
|
|
@ -4679,6 +4679,10 @@ static int do_open(struct nameidata *nd,
|
|||
if (unlikely(error))
|
||||
return error;
|
||||
}
|
||||
|
||||
if ((open_flag & __O_REGULAR) && !d_is_reg(nd->path.dentry))
|
||||
return -EFTYPE;
|
||||
|
||||
if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry))
|
||||
return -ENOTDIR;
|
||||
|
||||
|
|
|
|||
|
|
@ -2194,6 +2194,10 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
|
|||
break;
|
||||
case -EISDIR:
|
||||
case -ENOTDIR:
|
||||
if (open_flags & __O_REGULAR) {
|
||||
err = -EFTYPE;
|
||||
break;
|
||||
}
|
||||
goto no_open;
|
||||
case -ELOOP:
|
||||
if (!(open_flags & O_NOFOLLOW))
|
||||
|
|
|
|||
35
fs/open.c
35
fs/open.c
|
|
@ -960,7 +960,7 @@ static int do_dentry_open(struct file *f,
|
|||
if (f->f_mapping->a_ops && f->f_mapping->a_ops->direct_IO)
|
||||
f->f_mode |= FMODE_CAN_ODIRECT;
|
||||
|
||||
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
|
||||
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | __O_REGULAR);
|
||||
f->f_iocb_flags = iocb_flags(f);
|
||||
|
||||
file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);
|
||||
|
|
@ -1184,7 +1184,15 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
|
|||
int acc_mode = ACC_MODE(flags);
|
||||
|
||||
BUILD_BUG_ON_MSG(upper_32_bits(VALID_OPEN_FLAGS),
|
||||
"struct open_flags doesn't yet handle flags > 32 bits");
|
||||
"VALID_OPEN_FLAGS must fit in 32 bits");
|
||||
/* The whole point: OPENAT2_REGULAR must be unrepresentable in int. */
|
||||
BUILD_BUG_ON_MSG(!upper_32_bits(OPENAT2_REGULAR),
|
||||
"OPENAT2_REGULAR must live in the upper 32 bits of open_how::flags");
|
||||
/* Prevent a future bit collision between UAPI and internal carrier. */
|
||||
BUILD_BUG_ON_MSG(OPENAT2_REGULAR & VALID_OPEN_FLAGS,
|
||||
"OPENAT2_REGULAR must not alias any open()/openat() flag");
|
||||
BUILD_BUG_ON_MSG(__O_REGULAR & VALID_OPENAT2_FLAGS,
|
||||
"__O_REGULAR must not alias any user-visible flag");
|
||||
|
||||
/*
|
||||
* Strip flags that aren't relevant in determining struct open_flags.
|
||||
|
|
@ -1196,7 +1204,7 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
|
|||
* values before calling build_open_flags(), but openat2(2) checks all
|
||||
* of its arguments.
|
||||
*/
|
||||
if (flags & ~VALID_OPEN_FLAGS)
|
||||
if (flags & ~VALID_OPENAT2_FLAGS)
|
||||
return -EINVAL;
|
||||
if (how->resolve & ~VALID_RESOLVE_FLAGS)
|
||||
return -EINVAL;
|
||||
|
|
@ -1236,6 +1244,14 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
|
|||
if (!(acc_mode & MAY_WRITE))
|
||||
return -EINVAL;
|
||||
}
|
||||
/*
|
||||
* Asking to open a directory and a regular file at the same time is
|
||||
* contradictory.
|
||||
*/
|
||||
if ((flags & (O_DIRECTORY | OPENAT2_REGULAR)) ==
|
||||
(O_DIRECTORY | OPENAT2_REGULAR))
|
||||
return -EINVAL;
|
||||
|
||||
if (flags & O_PATH) {
|
||||
/* O_PATH only permits certain other flags to be set. */
|
||||
if (flags & ~O_PATH_FLAGS)
|
||||
|
|
@ -1252,6 +1268,19 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
|
|||
if (flags & __O_SYNC)
|
||||
flags |= O_DSYNC;
|
||||
|
||||
/*
|
||||
* Translate the upper-32-bit UAPI bit OPENAT2_REGULAR into the
|
||||
* kernel-internal lower-32-bit __O_REGULAR carrier so the bit
|
||||
* survives the assignment to op->open_flag (an int) below and the
|
||||
* subsequent flow through f->f_flags (unsigned int) and the
|
||||
* i_op->atomic_open() callback (unsigned). do_dentry_open() strips
|
||||
* __O_REGULAR before the file becomes visible to userspace.
|
||||
*/
|
||||
if (flags & OPENAT2_REGULAR) {
|
||||
flags &= ~OPENAT2_REGULAR;
|
||||
flags |= __O_REGULAR;
|
||||
}
|
||||
|
||||
op->open_flag = flags;
|
||||
|
||||
/* O_TRUNC implies we need access checks for write permissions */
|
||||
|
|
|
|||
|
|
@ -241,6 +241,12 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry,
|
|||
goto cifs_create_get_file_info;
|
||||
}
|
||||
|
||||
if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) {
|
||||
CIFSSMBClose(xid, tcon, fid->netfid);
|
||||
iput(newinode);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
if (S_ISDIR(newinode->i_mode)) {
|
||||
CIFSSMBClose(xid, tcon, fid->netfid);
|
||||
iput(newinode);
|
||||
|
|
@ -458,9 +464,15 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry,
|
|||
goto out_err;
|
||||
}
|
||||
|
||||
if (newinode && S_ISDIR(newinode->i_mode)) {
|
||||
rc = -EISDIR;
|
||||
goto out_err;
|
||||
if (newinode) {
|
||||
if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) {
|
||||
rc = -EFTYPE;
|
||||
goto out_err;
|
||||
}
|
||||
if (S_ISDIR(newinode->i_mode)) {
|
||||
rc = -EISDIR;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
*inode = newinode;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <linux/stat.h>
|
||||
#include <uapi/linux/fcntl.h>
|
||||
#include <uapi/linux/openat2.h>
|
||||
|
||||
/* List of all valid flags for the open/openat flags argument: */
|
||||
#define VALID_OPEN_FLAGS \
|
||||
|
|
@ -12,6 +13,23 @@
|
|||
FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
|
||||
O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH)
|
||||
|
||||
/* List of all valid flags for openat2(2)'s how->flags argument. */
|
||||
#define VALID_OPENAT2_FLAGS (VALID_OPEN_FLAGS | OPENAT2_REGULAR)
|
||||
|
||||
/*
|
||||
* Kernel-internal carrier for OPENAT2_REGULAR. The UAPI bit lives in the
|
||||
* upper 32 bits of open_how::flags so open()/openat() cannot encode it.
|
||||
* build_open_flags() translates it to this internal flag, which then
|
||||
* propagates through op->open_flag and f->f_flags exactly like __FMODE_EXEC.
|
||||
* do_dentry_open() strips it so userspace cannot observe it via
|
||||
* fcntl(F_GETFL).
|
||||
*
|
||||
* Bit 30 is not claimed by any O_* flag on any architecture and stays clear
|
||||
* of the sign bit of the int op->open_flag. fcntl_init() enforces that it
|
||||
* never aliases an open-flag bit.
|
||||
*/
|
||||
#define __O_REGULAR (1 << 30)
|
||||
|
||||
/* List of all valid flags for the how->resolve argument: */
|
||||
#define VALID_RESOLVE_FLAGS \
|
||||
(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
|
||||
|
|
|
|||
|
|
@ -122,4 +122,6 @@
|
|||
|
||||
#define EHWPOISON 133 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 134 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ struct open_how {
|
|||
__u64 resolve;
|
||||
};
|
||||
|
||||
/*
|
||||
* how->flags bits exclusive to openat2(2). These live in the upper 32 bits
|
||||
* of @flags so that they cannot be expressed by open(2) / openat(2), whose
|
||||
* @flags argument is a C int.
|
||||
*/
|
||||
#define OPENAT2_REGULAR ((__u64)1 << 32) /* Only open regular files. */
|
||||
|
||||
/* how->resolve flags for openat2(2). */
|
||||
#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
|
||||
(includes bind-mounts). */
|
||||
|
|
|
|||
|
|
@ -127,4 +127,6 @@
|
|||
|
||||
#define EHWPOISON 139 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 140 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@
|
|||
|
||||
#define EHWPOISON 168 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 169 /* Wrong file type for the intended operation */
|
||||
|
||||
#define EDQUOT 1133 /* Quota exceeded */
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -124,4 +124,6 @@
|
|||
|
||||
#define EHWPOISON 257 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 258 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -117,4 +117,6 @@
|
|||
|
||||
#define EHWPOISON 135 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 136 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -122,4 +122,6 @@
|
|||
|
||||
#define EHWPOISON 133 /* Memory page has hardware error */
|
||||
|
||||
#define EFTYPE 134 /* Wrong file type for the intended operation */
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -310,4 +310,53 @@ TEST_F(openat2, flag_validation)
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef OPENAT2_REGULAR
|
||||
#define OPENAT2_REGULAR ((__u64)1 << 32)
|
||||
#endif
|
||||
|
||||
#ifndef EFTYPE
|
||||
#define EFTYPE 134
|
||||
#endif
|
||||
|
||||
/* Kernel-internal carrier for OPENAT2_REGULAR (see __O_REGULAR in fcntl.h). */
|
||||
#ifndef __O_REGULAR
|
||||
#define __O_REGULAR (1 << 30)
|
||||
#endif
|
||||
|
||||
/* Verify that OPENAT2_REGULAR rejects non-regular files with EFTYPE. */
|
||||
TEST_F(openat2, regular_flag)
|
||||
{
|
||||
struct open_how how = {
|
||||
.flags = OPENAT2_REGULAR | O_RDONLY,
|
||||
};
|
||||
int fd;
|
||||
|
||||
fd = sys_openat2(AT_FDCWD, "/dev/null", &how);
|
||||
if (fd == -ENOENT)
|
||||
SKIP(return, "/dev/null does not exist");
|
||||
|
||||
EXPECT_EQ(-EFTYPE, fd) {
|
||||
TH_LOG("openat2 with OPENAT2_REGULAR should fail with %d (%s), got %d (%s)",
|
||||
-EFTYPE, strerror(EFTYPE), fd, strerror(-fd));
|
||||
}
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* open()/openat() must keep ignoring the internal __O_REGULAR bit. */
|
||||
TEST(legacy_openat_ignores_o_regular)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = openat(AT_FDCWD, "/dev/null", O_RDONLY | __O_REGULAR);
|
||||
if (fd < 0 && errno == ENOENT)
|
||||
SKIP(return, "/dev/null does not exist");
|
||||
|
||||
ASSERT_GE(fd, 0) {
|
||||
TH_LOG("legacy openat() must ignore the __O_REGULAR carrier bit, got errno %d (%s)",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user