vfs: check unlinked ancestors before mount
We check submounts before doing d_drop() on a non-empty directory dentry in NFS (have_submounts()), but we do not exclude a racing mount. Nor do we prevent mounts to be added to the disconnected subtree using relative paths after the d_drop(). This patch fixes these issues by checking for unlinked (unhashed, non-root) ancestors before proceeding with the mount. This is done with rename seqlock taken for write and with ->d_lock grabbed on each ancestor in turn, including our dentry itself. This ensures that the only one of check_submounts_and_drop() or has_unlinked_ancestor() can succeed. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
848ac114e8
commit
eed8100766
33
fs/dcache.c
33
fs/dcache.c
|
@ -1183,6 +1183,39 @@ int have_submounts(struct dentry *parent)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(have_submounts);
|
EXPORT_SYMBOL(have_submounts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called by mount code to set a mountpoint and check if the mountpoint is
|
||||||
|
* reachable (e.g. NFS can unhash a directory dentry and then the complete
|
||||||
|
* subtree can become unreachable).
|
||||||
|
*
|
||||||
|
* Only one of check_submounts_and_drop() and d_set_mounted() must succeed. For
|
||||||
|
* this reason take rename_lock and d_lock on dentry and ancestors.
|
||||||
|
*/
|
||||||
|
int d_set_mounted(struct dentry *dentry)
|
||||||
|
{
|
||||||
|
struct dentry *p;
|
||||||
|
int ret = -ENOENT;
|
||||||
|
write_seqlock(&rename_lock);
|
||||||
|
for (p = dentry->d_parent; !IS_ROOT(p); p = p->d_parent) {
|
||||||
|
/* Need exclusion wrt. check_submounts_and_drop() */
|
||||||
|
spin_lock(&p->d_lock);
|
||||||
|
if (unlikely(d_unhashed(p))) {
|
||||||
|
spin_unlock(&p->d_lock);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
spin_unlock(&p->d_lock);
|
||||||
|
}
|
||||||
|
spin_lock(&dentry->d_lock);
|
||||||
|
if (!d_unlinked(dentry)) {
|
||||||
|
dentry->d_flags |= DCACHE_MOUNTED;
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
spin_unlock(&dentry->d_lock);
|
||||||
|
out:
|
||||||
|
write_sequnlock(&rename_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Search the dentry child list of the specified parent,
|
* Search the dentry child list of the specified parent,
|
||||||
* and move any unused dentries to the end of the unused
|
* and move any unused dentries to the end of the unused
|
||||||
|
|
|
@ -126,6 +126,7 @@ extern int invalidate_inodes(struct super_block *, bool);
|
||||||
* dcache.c
|
* dcache.c
|
||||||
*/
|
*/
|
||||||
extern struct dentry *__d_alloc(struct super_block *, const struct qstr *);
|
extern struct dentry *__d_alloc(struct super_block *, const struct qstr *);
|
||||||
|
extern int d_set_mounted(struct dentry *dentry);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* read_write.c
|
* read_write.c
|
||||||
|
|
|
@ -611,6 +611,7 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry)
|
||||||
{
|
{
|
||||||
struct list_head *chain = mountpoint_hashtable + hash(NULL, dentry);
|
struct list_head *chain = mountpoint_hashtable + hash(NULL, dentry);
|
||||||
struct mountpoint *mp;
|
struct mountpoint *mp;
|
||||||
|
int ret;
|
||||||
|
|
||||||
list_for_each_entry(mp, chain, m_hash) {
|
list_for_each_entry(mp, chain, m_hash) {
|
||||||
if (mp->m_dentry == dentry) {
|
if (mp->m_dentry == dentry) {
|
||||||
|
@ -626,14 +627,12 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry)
|
||||||
if (!mp)
|
if (!mp)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
spin_lock(&dentry->d_lock);
|
ret = d_set_mounted(dentry);
|
||||||
if (d_unlinked(dentry)) {
|
if (ret) {
|
||||||
spin_unlock(&dentry->d_lock);
|
|
||||||
kfree(mp);
|
kfree(mp);
|
||||||
return ERR_PTR(-ENOENT);
|
return ERR_PTR(ret);
|
||||||
}
|
}
|
||||||
dentry->d_flags |= DCACHE_MOUNTED;
|
|
||||||
spin_unlock(&dentry->d_lock);
|
|
||||||
mp->m_dentry = dentry;
|
mp->m_dentry = dentry;
|
||||||
mp->m_count = 1;
|
mp->m_count = 1;
|
||||||
list_add(&mp->m_hash, chain);
|
list_add(&mp->m_hash, chain);
|
||||||
|
|
Loading…
Reference in New Issue