Merge branch 'stable-fodder' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull vfs stable fodder fixes from Al Viro: - acct_on() fix for deadlock caught by overlayfs folks - autofs RCU use-after-free SNAFU (->d_manage() can be called locklessly, so we need to RCU-delay freeing the objects it looks at) - (hopefully) the end of "do we need freeing this dentry RCU-delayed" whack-a-mole. * 'stable-fodder' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: autofs: fix use-after-free in lockless ->d_manage() dcache: sort the freeing-without-RCU-delay mess for good. acct_on(): don't mess with freeze protection
This commit is contained in:
commit
78438ce18f
|
@ -663,3 +663,8 @@ in your dentry operations instead.
|
|||
there, but that's it. Freeing memory in the callback is fine; doing
|
||||
more than that is possible, but requires a lot of care and is best
|
||||
avoided.
|
||||
--
|
||||
[mandatory]
|
||||
DCACHE_RCUACCESS is gone; having an RCU delay on dentry freeing is the
|
||||
default. DCACHE_NORCU opts out, and only d_alloc_pseudo() has any
|
||||
business doing so.
|
||||
|
|
|
@ -71,6 +71,7 @@ struct autofs_info {
|
|||
|
||||
kuid_t uid;
|
||||
kgid_t gid;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
#define AUTOFS_INF_EXPIRING (1<<0) /* dentry in the process of expiring */
|
||||
|
|
|
@ -36,7 +36,7 @@ void autofs_clean_ino(struct autofs_info *ino)
|
|||
|
||||
void autofs_free_ino(struct autofs_info *ino)
|
||||
{
|
||||
kfree(ino);
|
||||
kfree_rcu(ino, rcu);
|
||||
}
|
||||
|
||||
void autofs_kill_sb(struct super_block *sb)
|
||||
|
|
24
fs/dcache.c
24
fs/dcache.c
|
@ -344,7 +344,7 @@ static void dentry_free(struct dentry *dentry)
|
|||
}
|
||||
}
|
||||
/* if dentry was never visible to RCU, immediate free is OK */
|
||||
if (!(dentry->d_flags & DCACHE_RCUACCESS))
|
||||
if (dentry->d_flags & DCACHE_NORCU)
|
||||
__d_free(&dentry->d_u.d_rcu);
|
||||
else
|
||||
call_rcu(&dentry->d_u.d_rcu, __d_free);
|
||||
|
@ -1701,7 +1701,6 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
|
|||
struct dentry *dentry = __d_alloc(parent->d_sb, name);
|
||||
if (!dentry)
|
||||
return NULL;
|
||||
dentry->d_flags |= DCACHE_RCUACCESS;
|
||||
spin_lock(&parent->d_lock);
|
||||
/*
|
||||
* don't need child lock because it is not subject
|
||||
|
@ -1726,7 +1725,7 @@ struct dentry *d_alloc_cursor(struct dentry * parent)
|
|||
{
|
||||
struct dentry *dentry = d_alloc_anon(parent->d_sb);
|
||||
if (dentry) {
|
||||
dentry->d_flags |= DCACHE_RCUACCESS | DCACHE_DENTRY_CURSOR;
|
||||
dentry->d_flags |= DCACHE_DENTRY_CURSOR;
|
||||
dentry->d_parent = dget(parent);
|
||||
}
|
||||
return dentry;
|
||||
|
@ -1739,10 +1738,17 @@ struct dentry *d_alloc_cursor(struct dentry * parent)
|
|||
*
|
||||
* For a filesystem that just pins its dentries in memory and never
|
||||
* performs lookups at all, return an unhashed IS_ROOT dentry.
|
||||
* This is used for pipes, sockets et.al. - the stuff that should
|
||||
* never be anyone's children or parents. Unlike all other
|
||||
* dentries, these will not have RCU delay between dropping the
|
||||
* last reference and freeing them.
|
||||
*/
|
||||
struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
|
||||
{
|
||||
return __d_alloc(sb, name);
|
||||
struct dentry *dentry = __d_alloc(sb, name);
|
||||
if (likely(dentry))
|
||||
dentry->d_flags |= DCACHE_NORCU;
|
||||
return dentry;
|
||||
}
|
||||
EXPORT_SYMBOL(d_alloc_pseudo);
|
||||
|
||||
|
@ -1911,12 +1917,10 @@ struct dentry *d_make_root(struct inode *root_inode)
|
|||
|
||||
if (root_inode) {
|
||||
res = d_alloc_anon(root_inode->i_sb);
|
||||
if (res) {
|
||||
res->d_flags |= DCACHE_RCUACCESS;
|
||||
if (res)
|
||||
d_instantiate(res, root_inode);
|
||||
} else {
|
||||
else
|
||||
iput(root_inode);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -2781,9 +2785,7 @@ static void __d_move(struct dentry *dentry, struct dentry *target,
|
|||
copy_name(dentry, target);
|
||||
target->d_hash.pprev = NULL;
|
||||
dentry->d_parent->d_lockref.count++;
|
||||
if (dentry == old_parent)
|
||||
dentry->d_flags |= DCACHE_RCUACCESS;
|
||||
else
|
||||
if (dentry != old_parent) /* wasn't IS_ROOT */
|
||||
WARN_ON(!--old_parent->d_lockref.count);
|
||||
} else {
|
||||
target->d_parent = old_parent;
|
||||
|
|
|
@ -89,9 +89,7 @@ extern int sb_prepare_remount_readonly(struct super_block *);
|
|||
|
||||
extern void __init mnt_init(void);
|
||||
|
||||
extern int __mnt_want_write(struct vfsmount *);
|
||||
extern int __mnt_want_write_file(struct file *);
|
||||
extern void __mnt_drop_write(struct vfsmount *);
|
||||
extern void __mnt_drop_write_file(struct file *);
|
||||
|
||||
/*
|
||||
|
|
|
@ -85,13 +85,12 @@ slow:
|
|||
inode->i_fop = &ns_file_operations;
|
||||
inode->i_private = ns;
|
||||
|
||||
dentry = d_alloc_pseudo(mnt->mnt_sb, &empty_name);
|
||||
dentry = d_alloc_anon(mnt->mnt_sb);
|
||||
if (!dentry) {
|
||||
iput(inode);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
d_instantiate(dentry, inode);
|
||||
dentry->d_flags |= DCACHE_RCUACCESS;
|
||||
dentry->d_fsdata = (void *)ns->ops;
|
||||
d = atomic_long_cmpxchg(&ns->stashed, 0, (unsigned long)dentry);
|
||||
if (d) {
|
||||
|
|
|
@ -176,7 +176,6 @@ struct dentry_operations {
|
|||
* typically using d_splice_alias. */
|
||||
|
||||
#define DCACHE_REFERENCED 0x00000040 /* Recently used, don't discard. */
|
||||
#define DCACHE_RCUACCESS 0x00000080 /* Entry has ever been RCU-visible */
|
||||
|
||||
#define DCACHE_CANT_MOUNT 0x00000100
|
||||
#define DCACHE_GENOCIDE 0x00000200
|
||||
|
@ -217,6 +216,7 @@ struct dentry_operations {
|
|||
|
||||
#define DCACHE_PAR_LOOKUP 0x10000000 /* being looked up (with parent locked shared) */
|
||||
#define DCACHE_DENTRY_CURSOR 0x20000000
|
||||
#define DCACHE_NORCU 0x40000000 /* No RCU delay for freeing */
|
||||
|
||||
extern seqlock_t rename_lock;
|
||||
|
||||
|
|
|
@ -87,6 +87,8 @@ extern bool mnt_may_suid(struct vfsmount *mnt);
|
|||
|
||||
struct path;
|
||||
extern struct vfsmount *clone_private_mount(const struct path *path);
|
||||
extern int __mnt_want_write(struct vfsmount *);
|
||||
extern void __mnt_drop_write(struct vfsmount *);
|
||||
|
||||
struct file_system_type;
|
||||
extern struct vfsmount *fc_mount(struct fs_context *fc);
|
||||
|
|
|
@ -227,7 +227,7 @@ static int acct_on(struct filename *pathname)
|
|||
filp_close(file, NULL);
|
||||
return PTR_ERR(internal);
|
||||
}
|
||||
err = mnt_want_write(internal);
|
||||
err = __mnt_want_write(internal);
|
||||
if (err) {
|
||||
mntput(internal);
|
||||
kfree(acct);
|
||||
|
@ -252,7 +252,7 @@ static int acct_on(struct filename *pathname)
|
|||
old = xchg(&ns->bacct, &acct->pin);
|
||||
mutex_unlock(&acct->lock);
|
||||
pin_kill(old);
|
||||
mnt_drop_write(mnt);
|
||||
__mnt_drop_write(mnt);
|
||||
mntput(mnt);
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue