summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 13:00:36 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 13:00:36 -0800
commit45a43ac5acc90b8f4835eea92692f620e561a06b (patch)
tree254f816efd2f9ce1363509520921d7a88192ac56 /fs
parent543b9b63394ee67ecf5298fe42cbe65b21a16eac (diff)
parentdedfae78f00960d703badc500422d10e1f12b2bc (diff)
Merge tag 'vfs-7.0-rc1.misc.2' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull more misc vfs updates from Christian Brauner: "Features: - Optimize close_range() from O(range size) to O(active FDs) by using find_next_bit() on the open_fds bitmap instead of linearly scanning the entire requested range. This is a significant improvement for large-range close operations on sparse file descriptor tables. - Add FS_XFLAG_VERITY file attribute for fs-verity files, retrievable via FS_IOC_FSGETXATTR and file_getattr(). The flag is read-only. Add tracepoints for fs-verity enable and verify operations, replacing the previously removed debug printk's. - Prevent nfsd from exporting special kernel filesystems like pidfs and nsfs. These filesystems have custom ->open() and ->permission() export methods that are designed for open_by_handle_at(2) only and are incompatible with nfsd. Update the exportfs documentation accordingly. Fixes: - Fix KMSAN uninit-value in ovl_fill_real() where strcmp() was used on a non-null-terminated decrypted directory entry name from fscrypt. This triggered on encrypted lower layers when the decrypted name buffer contained uninitialized tail data. The fix also adds VFS-level name_is_dot(), name_is_dotdot(), and name_is_dot_dotdot() helpers, replacing various open-coded "." and ".." checks across the tree. - Fix read-only fsflags not being reset together with xflags in vfs_fileattr_set(). Currently harmless since no read-only xflags overlap with flags, but this would cause inconsistencies for any future shared read-only flag - Return -EREMOTE instead of -ESRCH from PIDFD_GET_INFO when the target process is in a different pid namespace. This lets userspace distinguish "process exited" from "process in another namespace", matching glibc's pidfd_getpid() behavior Cleanups: - Use C-string literals in the Rust seq_file bindings, replacing the kernel::c_str!() macro (available since Rust 1.77) - Fix typo in d_walk_ret enum comment, add porting notes for the readlink_copy() calling convention change" * tag 'vfs-7.0-rc1.misc.2' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: fs: add porting notes about readlink_copy() pidfs: return -EREMOTE when PIDFD_GET_INFO is called on another ns nfsd: do not allow exporting of special kernel filesystems exportfs: clarify the documentation of open()/permission() expotrfs ops fsverity: add tracepoints fs: add FS_XFLAG_VERITY for fs-verity files rust: seq_file: replace `kernel::c_str!` with C-Strings fs: dcache: fix typo in enum d_walk_ret comment ovl: use name_is_dot* helpers in readdir code fs: add helpers name_is_dot{,dot,_dotdot} ovl: Fix uninit-value in ovl_fill_real fs: reset read-only fsflags together with xflags fs/file: optimize close_range() complexity from O(N) to O(Sparse)
Diffstat (limited to 'fs')
-rw-r--r--fs/crypto/fname.c2
-rw-r--r--fs/dcache.c10
-rw-r--r--fs/ecryptfs/crypto.c2
-rw-r--r--fs/exportfs/expfs.c3
-rw-r--r--fs/f2fs/dir.c2
-rw-r--r--fs/f2fs/hash.c2
-rw-r--r--fs/file.c10
-rw-r--r--fs/file_attr.c10
-rw-r--r--fs/namei.c2
-rw-r--r--fs/nfsd/export.c8
-rw-r--r--fs/overlayfs/readdir.c41
-rw-r--r--fs/pidfs.c2
-rw-r--r--fs/smb/server/vfs.c2
-rw-r--r--fs/verity/enable.c4
-rw-r--r--fs/verity/fsverity_private.h2
-rw-r--r--fs/verity/init.c1
-rw-r--r--fs/verity/verify.c9
17 files changed, 64 insertions, 48 deletions
diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c
index a9a4432d12ba..629eb0d72e86 100644
--- a/fs/crypto/fname.c
+++ b/fs/crypto/fname.c
@@ -76,7 +76,7 @@ struct fscrypt_nokey_name {
static inline bool fscrypt_is_dot_dotdot(const struct qstr *str)
{
- return is_dot_dotdot(str->name, str->len);
+ return name_is_dot_dotdot(str->name, str->len);
}
/**
diff --git a/fs/dcache.c b/fs/dcache.c
index 2758fe7322d4..7ba1801d8132 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1298,8 +1298,8 @@ void shrink_dcache_sb(struct super_block *sb)
EXPORT_SYMBOL(shrink_dcache_sb);
/**
- * enum d_walk_ret - action to talke during tree walk
- * @D_WALK_CONTINUE: contrinue walk
+ * enum d_walk_ret - action to take during tree walk
+ * @D_WALK_CONTINUE: continue walk
* @D_WALK_QUIT: quit walk
* @D_WALK_NORETRY: quit when retry is needed
* @D_WALK_SKIP: skip this dentry and its children
@@ -1722,7 +1722,7 @@ void d_invalidate(struct dentry *dentry)
EXPORT_SYMBOL(d_invalidate);
/**
- * __d_alloc - allocate a dcache entry
+ * __d_alloc - allocate a dcache entry
* @sb: filesystem it will belong to
* @name: qstr of the name
*
@@ -1806,7 +1806,7 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
}
/**
- * d_alloc - allocate a dcache entry
+ * d_alloc - allocate a dcache entry
* @parent: parent of entry to allocate
* @name: qstr of the name
*
@@ -2546,7 +2546,7 @@ static void __d_rehash(struct dentry *entry)
}
/**
- * d_rehash - add an entry back to the hash
+ * d_rehash - add an entry back to the hash
* @entry: dentry to add to the hash
*
* Adds a dentry to the hash according to its name.
diff --git a/fs/ecryptfs/crypto.c b/fs/ecryptfs/crypto.c
index 260f8a4938b0..3c89f06c7453 100644
--- a/fs/ecryptfs/crypto.c
+++ b/fs/ecryptfs/crypto.c
@@ -1904,7 +1904,7 @@ int ecryptfs_decode_and_decrypt_filename(char **plaintext_name,
if ((mount_crypt_stat->flags & ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES) &&
!(mount_crypt_stat->flags & ECRYPTFS_ENCRYPTED_VIEW_ENABLED)) {
- if (is_dot_dotdot(name, name_size)) {
+ if (name_is_dot_dotdot(name, name_size)) {
rc = ecryptfs_copy_filename(plaintext_name,
plaintext_name_size,
name, name_size);
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c
index d3e55de4a2a2..6c9be60a3e48 100644
--- a/fs/exportfs/expfs.c
+++ b/fs/exportfs/expfs.c
@@ -253,7 +253,8 @@ static bool filldir_one(struct dir_context *ctx, const char *name, int len,
container_of(ctx, struct getdents_callback, ctx);
buf->sequence++;
- if (buf->ino == ino && len <= NAME_MAX && !is_dot_dotdot(name, len)) {
+ if (buf->ino == ino && len <= NAME_MAX &&
+ !name_is_dot_dotdot(name, len)) {
memcpy(buf->name, name, len);
buf->name[len] = '\0';
buf->found = 1;
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index be70dfb3b152..f70092e231f0 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -68,7 +68,7 @@ int f2fs_init_casefolded_name(const struct inode *dir,
int len;
if (IS_CASEFOLDED(dir) &&
- !is_dot_dotdot(fname->usr_fname->name, fname->usr_fname->len)) {
+ !name_is_dot_dotdot(fname->usr_fname->name, fname->usr_fname->len)) {
buf = f2fs_kmem_cache_alloc(f2fs_cf_name_slab,
GFP_NOFS, false, F2FS_SB(sb));
if (!buf)
diff --git a/fs/f2fs/hash.c b/fs/f2fs/hash.c
index 049ce50cec9b..14082fe5e6b2 100644
--- a/fs/f2fs/hash.c
+++ b/fs/f2fs/hash.c
@@ -100,7 +100,7 @@ void f2fs_hash_filename(const struct inode *dir, struct f2fs_filename *fname)
WARN_ON_ONCE(!name);
- if (is_dot_dotdot(name, len)) {
+ if (name_is_dot_dotdot(name, len)) {
fname->hash = 0;
return;
}
diff --git a/fs/file.c b/fs/file.c
index 0a4f3bdb2dec..51ddcff0081a 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -777,23 +777,29 @@ static inline void __range_close(struct files_struct *files, unsigned int fd,
unsigned int max_fd)
{
struct file *file;
+ struct fdtable *fdt;
unsigned n;
spin_lock(&files->file_lock);
- n = last_fd(files_fdtable(files));
+ fdt = files_fdtable(files);
+ n = last_fd(fdt);
max_fd = min(max_fd, n);
- for (; fd <= max_fd; fd++) {
+ for (fd = find_next_bit(fdt->open_fds, max_fd + 1, fd);
+ fd <= max_fd;
+ fd = find_next_bit(fdt->open_fds, max_fd + 1, fd + 1)) {
file = file_close_fd_locked(files, fd);
if (file) {
spin_unlock(&files->file_lock);
filp_close(file, files);
cond_resched();
spin_lock(&files->file_lock);
+ fdt = files_fdtable(files);
} else if (need_resched()) {
spin_unlock(&files->file_lock);
cond_resched();
spin_lock(&files->file_lock);
+ fdt = files_fdtable(files);
}
}
spin_unlock(&files->file_lock);
diff --git a/fs/file_attr.c b/fs/file_attr.c
index 42721427245a..6d2a298a786d 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -37,6 +37,8 @@ void fileattr_fill_xflags(struct file_kattr *fa, u32 xflags)
fa->flags |= FS_DAX_FL;
if (fa->fsx_xflags & FS_XFLAG_PROJINHERIT)
fa->flags |= FS_PROJINHERIT_FL;
+ if (fa->fsx_xflags & FS_XFLAG_VERITY)
+ fa->flags |= FS_VERITY_FL;
}
EXPORT_SYMBOL(fileattr_fill_xflags);
@@ -67,6 +69,8 @@ void fileattr_fill_flags(struct file_kattr *fa, u32 flags)
fa->fsx_xflags |= FS_XFLAG_DAX;
if (fa->flags & FS_PROJINHERIT_FL)
fa->fsx_xflags |= FS_XFLAG_PROJINHERIT;
+ if (fa->flags & FS_VERITY_FL)
+ fa->fsx_xflags |= FS_XFLAG_VERITY;
}
EXPORT_SYMBOL(fileattr_fill_flags);
@@ -142,8 +146,7 @@ static int file_attr_to_fileattr(const struct file_attr *fattr,
if (fattr->fa_xflags & ~mask)
return -EINVAL;
- fileattr_fill_xflags(fa, fattr->fa_xflags);
- fa->fsx_xflags &= ~FS_XFLAG_RDONLY_MASK;
+ fileattr_fill_xflags(fa, fattr->fa_xflags & ~FS_XFLAG_RDONLY_MASK);
fa->fsx_extsize = fattr->fa_extsize;
fa->fsx_projid = fattr->fa_projid;
fa->fsx_cowextsize = fattr->fa_cowextsize;
@@ -163,8 +166,7 @@ static int copy_fsxattr_from_user(struct file_kattr *fa,
if (xfa.fsx_xflags & ~mask)
return -EOPNOTSUPP;
- fileattr_fill_xflags(fa, xfa.fsx_xflags);
- fa->fsx_xflags &= ~FS_XFLAG_RDONLY_MASK;
+ fileattr_fill_xflags(fa, xfa.fsx_xflags & ~FS_XFLAG_RDONLY_MASK);
fa->fsx_extsize = xfa.fsx_extsize;
fa->fsx_nextents = xfa.fsx_nextents;
fa->fsx_projid = xfa.fsx_projid;
diff --git a/fs/namei.c b/fs/namei.c
index 8e7792de0000..5fe6cac48df8 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3088,7 +3088,7 @@ int lookup_noperm_common(struct qstr *qname, struct dentry *base)
if (!len)
return -EACCES;
- if (is_dot_dotdot(name, len))
+ if (name_is_dot_dotdot(name, len))
return -EACCES;
while (len--) {
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 2a1499f2ad19..09fe268fe2c7 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -427,7 +427,8 @@ static int check_export(const struct path *path, int *flags, unsigned char *uuid
* either a device number (so FS_REQUIRES_DEV needed)
* or an FSID number (so NFSEXP_FSID or ->uuid is needed).
* 2: We must be able to find an inode from a filehandle.
- * This means that s_export_op must be set.
+ * This means that s_export_op must be set and comply with
+ * the requirements for remote filesystem export.
* 3: We must not currently be on an idmapped mount.
*/
if (!(inode->i_sb->s_type->fs_flags & FS_REQUIRES_DEV) &&
@@ -437,8 +438,9 @@ static int check_export(const struct path *path, int *flags, unsigned char *uuid
return -EINVAL;
}
- if (!exportfs_can_decode_fh(inode->i_sb->s_export_op)) {
- dprintk("exp_export: export of invalid fs type.\n");
+ if (!exportfs_may_export(inode->i_sb->s_export_op)) {
+ dprintk("exp_export: export of invalid fs type (%s).\n",
+ inode->i_sb->s_type->name);
return -EINVAL;
}
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index 7fd415d7471e..bb09142b4e15 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -77,7 +77,8 @@ static int ovl_casefold(struct ovl_readdir_data *rdd, const char *str, int len,
char *cf_name;
int cf_len;
- if (!IS_ENABLED(CONFIG_UNICODE) || !rdd->map || is_dot_dotdot(str, len))
+ if (!IS_ENABLED(CONFIG_UNICODE) || !rdd->map ||
+ name_is_dot_dotdot(str, len))
return 0;
cf_name = kmalloc(NAME_MAX, GFP_KERNEL);
@@ -154,7 +155,7 @@ static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd,
return true;
/* Always recalc d_ino for parent */
- if (strcmp(p->name, "..") == 0)
+ if (name_is_dotdot(p->name, p->len))
return true;
/* If this is lower, then native d_ino will do */
@@ -165,7 +166,7 @@ static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd,
* Recalc d_ino for '.' and for all entries if dir is impure (contains
* copied up entries)
*/
- if ((p->name[0] == '.' && p->len == 1) ||
+ if (name_is_dot(p->name, p->len) ||
ovl_test_flag(OVL_IMPURE, d_inode(rdd->dentry)))
return true;
@@ -561,12 +562,12 @@ static int ovl_cache_update(const struct path *path, struct ovl_cache_entry *p,
if (!ovl_same_dev(ofs) && !p->check_xwhiteout)
goto out;
- if (p->name[0] == '.') {
+ if (name_is_dot_dotdot(p->name, p->len)) {
if (p->len == 1) {
this = dget(dir);
goto get;
}
- if (p->len == 2 && p->name[1] == '.') {
+ if (p->len == 2) {
/* we shall not be moved */
this = dget(dir->d_parent);
goto get;
@@ -666,8 +667,7 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
return err;
list_for_each_entry_safe(p, n, list, l_node) {
- if (strcmp(p->name, ".") != 0 &&
- strcmp(p->name, "..") != 0) {
+ if (!name_is_dot_dotdot(p->name, p->len)) {
err = ovl_cache_update(path, p, true);
if (err)
return err;
@@ -756,7 +756,7 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name,
struct dir_context *orig_ctx = rdt->orig_ctx;
bool res;
- if (rdt->parent_ino && strcmp(name, "..") == 0) {
+ if (rdt->parent_ino && name_is_dotdot(name, namelen)) {
ino = rdt->parent_ino;
} else if (rdt->cache) {
struct ovl_cache_entry *p;
@@ -1098,12 +1098,8 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
goto del_entry;
}
- if (p->name[0] == '.') {
- if (p->len == 1)
- goto del_entry;
- if (p->len == 2 && p->name[1] == '.')
- goto del_entry;
- }
+ if (name_is_dot_dotdot(p->name, p->len))
+ goto del_entry;
err = -ENOTEMPTY;
break;
@@ -1147,7 +1143,7 @@ static bool ovl_check_d_type(struct dir_context *ctx, const char *name,
container_of(ctx, struct ovl_readdir_data, ctx);
/* Even if d_type is not supported, DT_DIR is returned for . and .. */
- if (!strncmp(name, ".", namelen) || !strncmp(name, "..", namelen))
+ if (name_is_dot_dotdot(name, namelen))
return true;
if (d_type != DT_UNKNOWN)
@@ -1210,11 +1206,8 @@ static int ovl_workdir_cleanup_recurse(struct ovl_fs *ofs, const struct path *pa
list_for_each_entry(p, &list, l_node) {
struct dentry *dentry;
- if (p->name[0] == '.') {
- if (p->len == 1)
- continue;
- if (p->len == 2 && p->name[1] == '.')
- continue;
+ if (name_is_dot_dotdot(p->name, p->len)) {
+ continue;
} else if (incompat) {
pr_err("overlay with incompat feature '%s' cannot be mounted\n",
p->name);
@@ -1279,12 +1272,8 @@ int ovl_indexdir_cleanup(struct ovl_fs *ofs)
goto out;
list_for_each_entry(p, &list, l_node) {
- if (p->name[0] == '.') {
- if (p->len == 1)
- continue;
- if (p->len == 2 && p->name[1] == '.')
- continue;
- }
+ if (name_is_dot_dotdot(p->name, p->len))
+ continue;
index = ovl_lookup_upper_unlocked(ofs, p->name, indexdir, p->len);
if (IS_ERR(index)) {
err = PTR_ERR(index);
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 3ffa5e4707de..318253344b5c 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -360,7 +360,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
* namespace hierarchy.
*/
if (!pid_in_current_pidns(pid))
- return -ESRCH;
+ return -EREMOTE;
attr = READ_ONCE(pid->attr);
if (mask & PIDFD_INFO_EXIT) {
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index fbdc854dba0f..d08973b288e5 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1044,7 +1044,7 @@ static bool __dir_empty(struct dir_context *ctx, const char *name, int namlen,
struct ksmbd_readdir_data *buf;
buf = container_of(ctx, struct ksmbd_readdir_data, ctx);
- if (!is_dot_dotdot(name, namlen))
+ if (!name_is_dot_dotdot(name, namlen))
buf->dirent_count++;
return !buf->dirent_count;
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index c9448074cce1..42dfed1ce0ce 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -223,6 +223,8 @@ static int enable_verity(struct file *filp,
if (err)
goto out;
+ trace_fsverity_enable(inode, &params);
+
/*
* Start enabling verity on this file, serialized by the inode lock.
* Fail if verity is already enabled or is already being enabled.
@@ -265,6 +267,8 @@ static int enable_verity(struct file *filp,
goto rollback;
}
+ trace_fsverity_tree_done(inode, vi, &params);
+
/*
* Add the fsverity_info into the hash table before finishing the
* initialization so that we don't have to undo the enabling when memory
diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h
index 2887cb849cec..6e6854c19078 100644
--- a/fs/verity/fsverity_private.h
+++ b/fs/verity/fsverity_private.h
@@ -163,4 +163,6 @@ static inline void fsverity_init_signature(void)
void __init fsverity_init_workqueue(void);
+#include <trace/events/fsverity.h>
+
#endif /* _FSVERITY_PRIVATE_H */
diff --git a/fs/verity/init.c b/fs/verity/init.c
index 6e8d33b50240..d65206608583 100644
--- a/fs/verity/init.c
+++ b/fs/verity/init.c
@@ -5,6 +5,7 @@
* Copyright 2019 Google LLC
*/
+#define CREATE_TRACE_POINTS
#include "fsverity_private.h"
#include <linux/ratelimit.h>
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 31797f9b24d0..404ab68aaf9b 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -177,6 +177,9 @@ static bool verify_data_block(struct fsverity_info *vi,
/* Byte offset of the wanted hash relative to @addr */
unsigned int hoffset;
} hblocks[FS_VERITY_MAX_LEVELS];
+
+ trace_fsverity_verify_data_block(inode, params, data_pos);
+
/*
* The index of the previous level's block within that level; also the
* index of that block's hash within the current level.
@@ -255,6 +258,9 @@ static bool verify_data_block(struct fsverity_info *vi,
want_hash = _want_hash;
kunmap_local(haddr);
put_page(hpage);
+ trace_fsverity_merkle_hit(inode, data_pos, hblock_idx,
+ level,
+ hoffset >> params->log_digestsize);
goto descend;
}
hblocks[level].page = hpage;
@@ -273,6 +279,9 @@ descend:
unsigned long hblock_idx = hblocks[level - 1].index;
unsigned int hoffset = hblocks[level - 1].hoffset;
+ trace_fsverity_verify_merkle_block(inode, hblock_idx,
+ level, hoffset >> params->log_digestsize);
+
fsverity_hash_block(params, haddr, real_hash);
if (memcmp(want_hash, real_hash, hsize) != 0)
goto corrupted;