mirror of
https://github.com/torvalds/linux.git
synced 2026-01-31 05:07:06 +08:00
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()
This commit is contained in:
@@ -231,6 +231,8 @@ eventually gets pushed out to disk. This tunable is used to define when dirty
|
||||
inode is old enough to be eligible for writeback by the kernel flusher threads.
|
||||
And, it is also used as the interval to wakeup dirtytime_writeback thread.
|
||||
|
||||
Setting this to zero disables periodic dirtytime writeback.
|
||||
|
||||
|
||||
dirty_writeback_centisecs
|
||||
=========================
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
10
fs/dcache.c
10
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -851,6 +851,7 @@ static struct folio *__iomap_get_folio(struct iomap_iter *iter,
|
||||
}
|
||||
|
||||
folio_get(folio);
|
||||
folio_wait_stable(folio);
|
||||
return folio;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -1855,6 +1855,8 @@ struct dir_context {
|
||||
* INT_MAX unlimited
|
||||
*/
|
||||
int count;
|
||||
/* @actor supports these flags in d_type high bits */
|
||||
unsigned int dt_flags_mask;
|
||||
};
|
||||
|
||||
/* If OR-ed with d_type, pending signals are not checked */
|
||||
@@ -3524,7 +3526,9 @@ static inline bool dir_emit(struct dir_context *ctx,
|
||||
const char *name, int namelen,
|
||||
u64 ino, unsigned type)
|
||||
{
|
||||
return ctx->actor(ctx, name, namelen, ctx->pos, ino, type);
|
||||
unsigned int dt_mask = S_DT_MASK | ctx->dt_flags_mask;
|
||||
|
||||
return ctx->actor(ctx, name, namelen, ctx->pos, ino, type & dt_mask);
|
||||
}
|
||||
static inline bool dir_emit_dot(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user