Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eed81007 authored by Miklos Szeredi's avatar Miklos Szeredi Committed by Al Viro
Browse files

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: default avatarMiklos Szeredi <miklos@szeredi.hu>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 848ac114
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -1183,6 +1183,39 @@ int have_submounts(struct dentry *parent)
}
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,
 * and move any unused dentries to the end of the unused
+1 −0
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ extern int invalidate_inodes(struct super_block *, bool);
 * dcache.c
 */
extern struct dentry *__d_alloc(struct super_block *, const struct qstr *);
extern int d_set_mounted(struct dentry *dentry);

/*
 * read_write.c
+5 −6
Original line number Diff line number Diff line
@@ -611,6 +611,7 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry)
{
	struct list_head *chain = mountpoint_hashtable + hash(NULL, dentry);
	struct mountpoint *mp;
	int ret;

	list_for_each_entry(mp, chain, m_hash) {
		if (mp->m_dentry == dentry) {
@@ -626,14 +627,12 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry)
	if (!mp)
		return ERR_PTR(-ENOMEM);

	spin_lock(&dentry->d_lock);
	if (d_unlinked(dentry)) {
		spin_unlock(&dentry->d_lock);
	ret = d_set_mounted(dentry);
	if (ret) {
		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_count = 1;
	list_add(&mp->m_hash, chain);