Re: How to handle non-local renames?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, 2006-09-19 at 10:24 +0200, Miklos Szeredi wrote:
> > A: cd /mnt/a/b
> > B: mv /mnt/a/b /mnt
> > B: mv /mnt/a /mnt/b
> > A: cd a
> 
> I think with this it's possible (though not easy) to deadlock NFS:

Actually, it is dead(sic!) easy. See my testcase in

  http://bugzilla.kernel.org/show_bug.cgi?id=7178

Sigh...

I'm thinking something along the lines of the following patch on top of
David's d_materialise_dentry(), which is already in the 2.6.18-mm
kernels. Still untested (I'm working on that), but it attempts to do
what you were proposing:

        1) If instantiating a directory, check for aliases
        2) If an alias is found in the dcache, attempt to rename the
        existing dentry instead of instantiating the alias.
        3) Return ELOOP if the alias turns out to be a parent.

Cheers,
  Trond
--- Begin Message ---
If the caller tries to instantiate a directory using an inode that already
has a dentry alias, then we attempt to rename the existing dentry instead
of instantiating a new one. Fail with an ELOOP error if the rename would
affect one of our parent directories.

This behaviour is needed in order to avoid issues such as

  http://bugzilla.kernel.org/show_bug.cgi?id=7178

Signed-off-by: Trond Myklebust <Trond.Myklebust@xxxxxxxxxx>
---

 fs/dcache.c  |   79 +++++++++++++++++++++++++++++++++++++---------------------
 fs/nfs/dir.c |    7 ++++-
 2 files changed, 56 insertions(+), 30 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 17b392a..2bbf422 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1337,23 +1337,21 @@ static void switch_names(struct dentry *
  * deleted it.
  */
  
-/**
- * d_move - move a dentry
+/*
+ * d_move_locked - move a dentry
  * @dentry: entry to move
  * @target: new dentry
  *
  * Update the dcache to reflect the move of a file name. Negative
  * dcache entries should not be moved in this way.
  */
-
-void d_move(struct dentry * dentry, struct dentry * target)
+static void d_move_locked(struct dentry * dentry, struct dentry * target)
 {
 	struct hlist_head *list;
 
 	if (!dentry->d_inode)
 		printk(KERN_WARNING "VFS: moving negative dcache entry\n");
 
-	spin_lock(&dcache_lock);
 	write_seqlock(&rename_lock);
 	/*
 	 * XXXX: do we really need to take target->d_lock?
@@ -1404,10 +1402,39 @@ already_unhashed:
 	fsnotify_d_move(dentry);
 	spin_unlock(&dentry->d_lock);
 	write_sequnlock(&rename_lock);
+}
+
+/**
+ * d_move - move a dentry
+ * @dentry: entry to move
+ * @target: new dentry
+ *
+ * Update the dcache to reflect the move of a file name. Negative
+ * dcache entries should not be moved in this way.
+ */
+
+void d_move(struct dentry * dentry, struct dentry * target)
+{
+	spin_lock(&dcache_lock);
+	d_move_locked(dentry, target);
 	spin_unlock(&dcache_lock);
 }
 
 /*
+ * Reject any lookup loops due to malicious remote servers
+ */
+static int d_detect_loops(struct dentry *dentry, struct inode *inode)
+{
+	struct dentry *p;
+
+	for (p = dentry; p->d_parent != p; p = p->d_parent) {
+		if (p->d_parent->d_inode == inode)
+			return -ELOOP;
+	}
+	return 0;
+}
+
+/*
  * Prepare an anonymous dentry for life in the superblock's dentry tree as a
  * named dentry in place of the dentry to be replaced.
  */
@@ -1461,26 +1488,22 @@ struct dentry *d_materialise_unique(stru
 		goto found_lock;
 	}
 
-	/* See if a disconnected directory already exists as an anonymous root
-	 * that we should splice into the tree instead */
-	if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) {
-		spin_lock(&alias->d_lock);
-
-		/* Is this a mountpoint that we could splice into our tree? */
-		if (IS_ROOT(alias))
-			goto connect_mountpoint;
-
-		if (alias->d_name.len == dentry->d_name.len &&
-		    alias->d_parent == dentry->d_parent &&
-		    memcmp(alias->d_name.name,
-			   dentry->d_name.name,
-			   dentry->d_name.len) == 0)
-			goto replace_with_alias;
-
-		spin_unlock(&alias->d_lock);
-
-		/* Doh! Seem to be aliasing directories for some reason... */
-		dput(alias);
+	if (S_ISDIR(inode->i_mode)) {
+		actual = ERR_PTR(d_detect_loops(dentry, inode));
+		if (IS_ERR(actual))
+			goto out_unlock;
+		/* Does an aliased dentry already exist? */
+		alias = __d_find_alias(inode, 0);
+		if (alias) {
+			actual = alias;
+			/* Is this an anonymous mountpoint that we could splice
+			 * into our tree? */
+			if (IS_ROOT(alias))
+				goto connect_mountpoint;
+			/* Nope, but we must(!) avoid directory aliasing */
+			d_move_locked(alias, dentry);
+			goto out_unlock;
+		}
 	}
 
 	/* Add a unique reference */
@@ -1495,6 +1518,7 @@ found_lock:
 found:
 	_d_rehash(actual);
 	spin_unlock(&actual->d_lock);
+out_unlock:
 	spin_unlock(&dcache_lock);
 
 	if (actual == dentry) {
@@ -1507,12 +1531,9 @@ found:
 
 	/* Convert the anonymous/root alias into an ordinary dentry */
 connect_mountpoint:
+	spin_lock(&alias->d_lock);
 	__d_materialise_dentry(dentry, alias);
-
-	/* Replace the candidate dentry with the alias in the tree */
-replace_with_alias:
 	__d_drop(alias);
-	actual = alias;
 	goto found;
 
 shouldnt_be_hashed:
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 3419c2d..aca90cb 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -933,8 +933,11 @@ static struct dentry *nfs_lookup(struct 
 
 no_entry:
 	res = d_materialise_unique(dentry, inode);
-	if (res != NULL)
+	if (res != NULL) {
+		if (IS_ERR(res))
+			goto out_unlock;
 		dentry = res;
+	}
 	nfs_renew_times(dentry);
 	nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
 out_unlock:
@@ -1130,6 +1133,8 @@ static struct dentry *nfs_readdir_lookup
 	alias = d_materialise_unique(dentry, inode);
 	if (alias != NULL) {
 		dput(dentry);
+		if (IS_ERR(alias))
+			return NULL;
 		dentry = alias;
 	}
 

--- End Message ---

[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux