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:
Miklos Szeredi 2013-09-05 14:39:11 +02:00 committed by Al Viro
parent 848ac114e8
commit eed8100766
3 changed files with 39 additions and 6 deletions

View File

@ -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

View File

@ -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

View File

@ -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);