summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-01-26 09:30:48 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-01-26 09:30:48 -0800
commitfcb70a56f4d81450114034b2c61f48ce7444a0e2 (patch)
tree009315e7fdaafff838746fe2c8a1532802067738 /fs
parent63804fed149a6750ffd28610c5c1c98cce6bd377 (diff)
parent6358461178ca29a87c66495f1ce854388b0107c3 (diff)
downloadlinux-next-fcb70a56f4d81450114034b2c61f48ce7444a0e2.tar.gz
linux-next-fcb70a56f4d81450114034b2c61f48ce7444a0e2.zip
Merge tag 'vfs-6.19-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner: - Fix the the buggy conversion of fuse_reverse_inval_entry() introduced during the creation rework - Disallow nfs delegation requests for directories by setting simple_nosetlease() - Require an opt-in for getting readdir flag bits outside of S_DT_MASK set in d_type - Fix scheduling delayed writeback work by only scheduling when the dirty time expiry interval is non-zero and cancel the delayed work if the interval is set to zero - Use rounded_jiffies_interval for dirty time work - Check the return value of sb_set_blocksize() for romfs - Wait for batched folios to be stable in __iomap_get_folio() - Use private naming for fuse hash size - Fix the stale dentry cleanup to prevent a race that causes a UAF * tag 'vfs-6.19-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: vfs: document d_dispose_if_unused() fuse: shrink once after all buckets have been scanned fuse: clean up fuse_dentry_tree_work() fuse: add need_resched() before unlocking bucket fuse: make sure dentry is evicted if stale fuse: fix race when disposing stale dentries fuse: use private naming for fuse hash size writeback: use round_jiffies_relative for dirtytime_work iomap: wait for batched folios to be stable in __iomap_get_folio romfs: check sb_set_blocksize() return value docs: clarify that dirtytime_expire_seconds=0 disables writeback writeback: fix 100% CPU usage when dirtytime_expire_interval is 0 readdir: require opt-in for d_type flags vboxsf: don't allow delegations to be set on directories ceph: don't allow delegations to be set on directories gfs2: don't allow delegations to be set on directories 9p: don't allow delegations to be set on directories smb/client: properly disallow delegations on directories nfs: properly disallow delegation requests on directories fuse: fix conversion of fuse_reverse_inval_entry() to start_removing()
Diffstat (limited to 'fs')
-rw-r--r--fs/9p/vfs_dir.c2
-rw-r--r--fs/ceph/dir.c2
-rw-r--r--fs/dcache.c10
-rw-r--r--fs/fs-writeback.c16
-rw-r--r--fs/fuse/dir.c66
-rw-r--r--fs/gfs2/file.c1
-rw-r--r--fs/iomap/buffered-io.c1
-rw-r--r--fs/nfs/dir.c1
-rw-r--r--fs/nfs/nfs4file.c2
-rw-r--r--fs/readdir.c3
-rw-r--r--fs/romfs/super.c5
-rw-r--r--fs/smb/client/cifsfs.c4
-rw-r--r--fs/vboxsf/dir.c1
13 files changed, 75 insertions, 39 deletions
diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c
index e0d34e4e9076..af7f72abbb76 100644
--- a/fs/9p/vfs_dir.c
+++ b/fs/9p/vfs_dir.c
@@ -242,6 +242,7 @@ const struct file_operations v9fs_dir_operations = {
.iterate_shared = v9fs_dir_readdir,
.open = v9fs_file_open,
.release = v9fs_dir_release,
+ .setlease = simple_nosetlease,
};
const struct file_operations v9fs_dir_operations_dotl = {
@@ -251,4 +252,5 @@ const struct file_operations v9fs_dir_operations_dotl = {
.open = v9fs_file_open,
.release = v9fs_dir_release,
.fsync = v9fs_file_fsync_dotl,
+ .setlease = simple_nosetlease,
};
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 86d7aa594ea9..804588524cd5 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -2214,6 +2214,7 @@ const struct file_operations ceph_dir_fops = {
.fsync = ceph_fsync,
.lock = ceph_lock,
.flock = ceph_flock,
+ .setlease = simple_nosetlease,
};
const struct file_operations ceph_snapdir_fops = {
@@ -2221,6 +2222,7 @@ const struct file_operations ceph_snapdir_fops = {
.llseek = ceph_dir_llseek,
.open = ceph_open,
.release = ceph_release,
+ .setlease = simple_nosetlease,
};
const struct inode_operations ceph_dir_iops = {
diff --git a/fs/dcache.c b/fs/dcache.c
index dc2fff4811d1..66dd1bb830d1 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1104,6 +1104,16 @@ struct dentry *d_find_alias_rcu(struct inode *inode)
return de;
}
+/**
+ * d_dispose_if_unused - move unreferenced dentries to shrink list
+ * @dentry: dentry in question
+ * @dispose: head of shrink list
+ *
+ * If dentry has no external references, move it to shrink list.
+ *
+ * NOTE!!! The caller is responsible for preventing eviction of the dentry by
+ * holding dentry->d_inode->i_lock or equivalent.
+ */
void d_dispose_if_unused(struct dentry *dentry, struct list_head *dispose)
{
spin_lock(&dentry->d_lock);
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index baa2f2141146..5444fc706ac7 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -2492,7 +2492,9 @@ static void wakeup_dirtytime_writeback(struct work_struct *w)
wb_wakeup(wb);
}
rcu_read_unlock();
- schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ);
+ if (dirtytime_expire_interval)
+ schedule_delayed_work(&dirtytime_work,
+ round_jiffies_relative(dirtytime_expire_interval * HZ));
}
static int dirtytime_interval_handler(const struct ctl_table *table, int write,
@@ -2501,8 +2503,12 @@ static int dirtytime_interval_handler(const struct ctl_table *table, int write,
int ret;
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
- if (ret == 0 && write)
- mod_delayed_work(system_percpu_wq, &dirtytime_work, 0);
+ if (ret == 0 && write) {
+ if (dirtytime_expire_interval)
+ mod_delayed_work(system_percpu_wq, &dirtytime_work, 0);
+ else
+ cancel_delayed_work_sync(&dirtytime_work);
+ }
return ret;
}
@@ -2519,7 +2525,9 @@ static const struct ctl_table vm_fs_writeback_table[] = {
static int __init start_dirtytime_writeback(void)
{
- schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ);
+ if (dirtytime_expire_interval)
+ schedule_delayed_work(&dirtytime_work,
+ round_jiffies_relative(dirtytime_expire_interval * HZ));
register_sysctl_init("vm", vm_fs_writeback_table);
return 0;
}
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4b6b3d2758ff..3927cb069236 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -32,9 +32,9 @@ struct dentry_bucket {
spinlock_t lock;
};
-#define HASH_BITS 5
-#define HASH_SIZE (1 << HASH_BITS)
-static struct dentry_bucket dentry_hash[HASH_SIZE];
+#define FUSE_HASH_BITS 5
+#define FUSE_HASH_SIZE (1 << FUSE_HASH_BITS)
+static struct dentry_bucket dentry_hash[FUSE_HASH_SIZE];
struct delayed_work dentry_tree_work;
/* Minimum invalidation work queue frequency */
@@ -83,7 +83,7 @@ MODULE_PARM_DESC(inval_wq,
static inline struct dentry_bucket *get_dentry_bucket(struct dentry *dentry)
{
- int i = hash_ptr(dentry, HASH_BITS);
+ int i = hash_ptr(dentry, FUSE_HASH_BITS);
return &dentry_hash[i];
}
@@ -164,25 +164,31 @@ static void fuse_dentry_tree_work(struct work_struct *work)
struct rb_node *node;
int i;
- for (i = 0; i < HASH_SIZE; i++) {
+ for (i = 0; i < FUSE_HASH_SIZE; i++) {
spin_lock(&dentry_hash[i].lock);
node = rb_first(&dentry_hash[i].tree);
while (node) {
fd = rb_entry(node, struct fuse_dentry, node);
- if (time_after64(get_jiffies_64(), fd->time)) {
- rb_erase(&fd->node, &dentry_hash[i].tree);
- RB_CLEAR_NODE(&fd->node);
+ if (!time_before64(fd->time, get_jiffies_64()))
+ break;
+
+ rb_erase(&fd->node, &dentry_hash[i].tree);
+ RB_CLEAR_NODE(&fd->node);
+ spin_lock(&fd->dentry->d_lock);
+ /* If dentry is still referenced, let next dput release it */
+ fd->dentry->d_flags |= DCACHE_OP_DELETE;
+ spin_unlock(&fd->dentry->d_lock);
+ d_dispose_if_unused(fd->dentry, &dispose);
+ if (need_resched()) {
spin_unlock(&dentry_hash[i].lock);
- d_dispose_if_unused(fd->dentry, &dispose);
cond_resched();
spin_lock(&dentry_hash[i].lock);
- } else
- break;
+ }
node = rb_first(&dentry_hash[i].tree);
}
spin_unlock(&dentry_hash[i].lock);
- shrink_dentry_list(&dispose);
}
+ shrink_dentry_list(&dispose);
if (inval_wq)
schedule_delayed_work(&dentry_tree_work,
@@ -213,7 +219,7 @@ void fuse_dentry_tree_init(void)
{
int i;
- for (i = 0; i < HASH_SIZE; i++) {
+ for (i = 0; i < FUSE_HASH_SIZE; i++) {
spin_lock_init(&dentry_hash[i].lock);
dentry_hash[i].tree = RB_ROOT;
}
@@ -227,7 +233,7 @@ void fuse_dentry_tree_cleanup(void)
inval_wq = 0;
cancel_delayed_work_sync(&dentry_tree_work);
- for (i = 0; i < HASH_SIZE; i++)
+ for (i = 0; i < FUSE_HASH_SIZE; i++)
WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree));
}
@@ -479,18 +485,12 @@ static int fuse_dentry_init(struct dentry *dentry)
return 0;
}
-static void fuse_dentry_prune(struct dentry *dentry)
+static void fuse_dentry_release(struct dentry *dentry)
{
struct fuse_dentry *fd = dentry->d_fsdata;
if (!RB_EMPTY_NODE(&fd->node))
fuse_dentry_tree_del_node(dentry);
-}
-
-static void fuse_dentry_release(struct dentry *dentry)
-{
- struct fuse_dentry *fd = dentry->d_fsdata;
-
kfree_rcu(fd, rcu);
}
@@ -527,7 +527,6 @@ const struct dentry_operations fuse_dentry_operations = {
.d_revalidate = fuse_dentry_revalidate,
.d_delete = fuse_dentry_delete,
.d_init = fuse_dentry_init,
- .d_prune = fuse_dentry_prune,
.d_release = fuse_dentry_release,
.d_automount = fuse_dentry_automount,
};
@@ -1584,8 +1583,8 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
{
int err = -ENOTDIR;
struct inode *parent;
- struct dentry *dir;
- struct dentry *entry;
+ struct dentry *dir = NULL;
+ struct dentry *entry = NULL;
parent = fuse_ilookup(fc, parent_nodeid, NULL);
if (!parent)
@@ -1598,11 +1597,19 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
dir = d_find_alias(parent);
if (!dir)
goto put_parent;
-
- entry = start_removing_noperm(dir, name);
- dput(dir);
- if (IS_ERR(entry))
- goto put_parent;
+ while (!entry) {
+ struct dentry *child = try_lookup_noperm(name, dir);
+ if (!child || IS_ERR(child))
+ goto put_parent;
+ entry = start_removing_dentry(dir, child);
+ dput(child);
+ if (IS_ERR(entry))
+ goto put_parent;
+ if (!d_same_name(entry, dir, name)) {
+ end_removing(entry);
+ entry = NULL;
+ }
+ }
fuse_dir_changed(parent);
if (!(flags & FUSE_EXPIRE_ONLY))
@@ -1640,6 +1647,7 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
end_removing(entry);
put_parent:
+ dput(dir);
iput(parent);
return err;
}
diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c
index b2d23c98c996..86376f0dbf3a 100644
--- a/fs/gfs2/file.c
+++ b/fs/gfs2/file.c
@@ -1608,6 +1608,7 @@ const struct file_operations gfs2_dir_fops = {
.lock = gfs2_lock,
.flock = gfs2_flock,
.llseek = default_llseek,
+ .setlease = simple_nosetlease,
.fop_flags = FOP_ASYNC_LOCK,
};
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index fd9a2cf95620..6beb876658c0 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -851,6 +851,7 @@ static struct folio *__iomap_get_folio(struct iomap_iter *iter,
}
folio_get(folio);
+ folio_wait_stable(folio);
return folio;
}
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 8f9ea79b7882..e3654f4a1b9a 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -66,6 +66,7 @@ const struct file_operations nfs_dir_operations = {
.open = nfs_opendir,
.release = nfs_closedir,
.fsync = nfs_fsync_dir,
+ .setlease = simple_nosetlease,
};
const struct address_space_operations nfs_dir_aops = {
diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index 7317f26892c5..7f43e890d356 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -431,8 +431,6 @@ void nfs42_ssc_unregister_ops(void)
static int nfs4_setlease(struct file *file, int arg, struct file_lease **lease,
void **priv)
{
- if (!S_ISREG(file_inode(file)->i_mode))
- return -EINVAL;
return nfs4_proc_setlease(file, arg, lease, priv);
}
diff --git a/fs/readdir.c b/fs/readdir.c
index 7764b8638978..73707b6816e9 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -316,6 +316,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd,
struct getdents_callback buf = {
.ctx.actor = filldir,
.ctx.count = count,
+ .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent
};
int error;
@@ -400,6 +401,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd,
struct getdents_callback64 buf = {
.ctx.actor = filldir64,
.ctx.count = count,
+ .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent
};
int error;
@@ -569,6 +571,7 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
struct compat_getdents_callback buf = {
.ctx.actor = compat_filldir,
.ctx.count = count,
+ .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent,
};
int error;
diff --git a/fs/romfs/super.c b/fs/romfs/super.c
index 360b00854115..ac55193bf398 100644
--- a/fs/romfs/super.c
+++ b/fs/romfs/super.c
@@ -458,7 +458,10 @@ static int romfs_fill_super(struct super_block *sb, struct fs_context *fc)
#ifdef CONFIG_BLOCK
if (!sb->s_mtd) {
- sb_set_blocksize(sb, ROMBSIZE);
+ if (!sb_set_blocksize(sb, ROMBSIZE)) {
+ errorf(fc, "romfs: unable to set blocksize\n");
+ return -EINVAL;
+ }
} else {
sb->s_blocksize = ROMBSIZE;
sb->s_blocksize_bits = blksize_bits(ROMBSIZE);
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index d9664634144d..a3dc7cb1ab54 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -1149,9 +1149,6 @@ cifs_setlease(struct file *file, int arg, struct file_lease **lease, void **priv
struct inode *inode = file_inode(file);
struct cifsFileInfo *cfile = file->private_data;
- if (!S_ISREG(inode->i_mode))
- return -EINVAL;
-
/* Check if file is oplocked if this is request for new lease */
if (arg == F_UNLCK ||
((arg == F_RDLCK) && CIFS_CACHE_READ(CIFS_I(inode))) ||
@@ -1712,6 +1709,7 @@ const struct file_operations cifs_dir_ops = {
.remap_file_range = cifs_remap_file_range,
.llseek = generic_file_llseek,
.fsync = cifs_dir_fsync,
+ .setlease = simple_nosetlease,
};
static void
diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c
index 42bedc4ec7af..230d7589d15c 100644
--- a/fs/vboxsf/dir.c
+++ b/fs/vboxsf/dir.c
@@ -186,6 +186,7 @@ const struct file_operations vboxsf_dir_fops = {
.release = vboxsf_dir_release,
.read = generic_read_dir,
.llseek = generic_file_llseek,
+ .setlease = simple_nosetlease,
};
/*