Blame SOURCES/0005-ecryptfs-fix-unlink-and-rmdir-in-face-of-underlying-.patch

Kmods SIG c540c3
From bcf0d9d4b76976f892154efdfc509b256fd898e8 Mon Sep 17 00:00:00 2001
Kmods SIG c540c3
From: Al Viro <viro@zeniv.linux.org.uk>
Kmods SIG c540c3
Date: Sun, 3 Nov 2019 12:07:15 -0500
Kmods SIG c540c3
Subject: [Backport bcf0d9d4b769] ecryptfs: fix unlink and rmdir in face of
Kmods SIG c540c3
 underlying fs modifications
Kmods SIG c540c3
Kmods SIG c540c3
A problem similar to the one caught in commit 74dd7c97ea2a ("ecryptfs_rename():
Kmods SIG c540c3
verify that lower dentries are still OK after lock_rename()") exists for
Kmods SIG c540c3
unlink/rmdir as well.
Kmods SIG c540c3
Kmods SIG c540c3
Instead of playing with dget_parent() of underlying dentry of victim
Kmods SIG c540c3
and hoping it's the same as underlying dentry of our directory,
Kmods SIG c540c3
do the following:
Kmods SIG c540c3
        * find the underlying dentry of victim
Kmods SIG c540c3
        * find the underlying directory of victim's parent (stable
Kmods SIG c540c3
since the victim is ecryptfs dentry and inode of its parent is
Kmods SIG c540c3
held exclusive by the caller).
Kmods SIG c540c3
        * lock the inode of dentry underlying the victim's parent
Kmods SIG c540c3
        * check that underlying dentry of victim is still hashed and
Kmods SIG c540c3
has the right parent - it can be moved, but it can't be moved to/from
Kmods SIG c540c3
the directory we are holding exclusive.  So while ->d_parent itself
Kmods SIG c540c3
might not be stable, the result of comparison is.
Kmods SIG c540c3
Kmods SIG c540c3
If the check passes, everything is fine - underlying directory is locked,
Kmods SIG c540c3
underlying victim is still a child of that directory and we can go ahead
Kmods SIG c540c3
and feed them to vfs_unlink().  As in the current mainline we need to
Kmods SIG c540c3
pin the underlying dentry of victim, so that it wouldn't go negative under
Kmods SIG c540c3
us, but that's the only temporary reference that needs to be grabbed there.
Kmods SIG c540c3
Underlying dentry of parent won't go away (it's pinned by the parent,
Kmods SIG c540c3
which is held by caller), so there's no need to grab it.
Kmods SIG c540c3
Kmods SIG c540c3
The same problem (with the same solution) exists for rmdir.  Moreover,
Kmods SIG c540c3
rename gets simpler and more robust with the same "don't bother with
Kmods SIG c540c3
dget_parent()" approach.
Kmods SIG c540c3
Kmods SIG c540c3
Fixes: 74dd7c97ea2 "ecryptfs_rename(): verify that lower dentries are still OK after lock_rename()"
Kmods SIG c540c3
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Kmods SIG c540c3
---
Kmods SIG c540c3
 src/inode.c | 65 ++++++++++++++++++++++++++++-----------------
Kmods SIG c540c3
 1 file changed, 40 insertions(+), 25 deletions(-)
Kmods SIG c540c3
Kmods SIG c540c3
diff --git a/src/inode.c b/src/inode.c
Kmods SIG c540c3
index 18426f4855f11b3e561f1cc35ee381fac3f2cfcc..a905d5f4f3b0726303595e39ba40309d68baa422 100644
Kmods SIG c540c3
--- a/src/inode.c
Kmods SIG c540c3
+++ b/src/inode.c
Kmods SIG c540c3
@@ -128,13 +128,20 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
Kmods SIG c540c3
 			      struct inode *inode)
Kmods SIG c540c3
 {
Kmods SIG c540c3
 	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
Kmods SIG c540c3
-	struct inode *lower_dir_inode = ecryptfs_inode_to_lower(dir);
Kmods SIG c540c3
 	struct dentry *lower_dir_dentry;
Kmods SIG c540c3
+	struct inode *lower_dir_inode;
Kmods SIG c540c3
 	int rc;
Kmods SIG c540c3
 
Kmods SIG c540c3
-	dget(lower_dentry);
Kmods SIG c540c3
-	lower_dir_dentry = lock_parent(lower_dentry);
Kmods SIG c540c3
-	rc = vfs_unlink(lower_dir_inode, lower_dentry, NULL);
Kmods SIG c540c3
+	lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent);
Kmods SIG c540c3
+	lower_dir_inode = d_inode(lower_dir_dentry);
Kmods SIG c540c3
+	inode_lock_nested(lower_dir_inode, I_MUTEX_PARENT);
Kmods SIG c540c3
+	dget(lower_dentry);	// don't even try to make the lower negative
Kmods SIG c540c3
+	if (lower_dentry->d_parent != lower_dir_dentry)
Kmods SIG c540c3
+		rc = -EINVAL;
Kmods SIG c540c3
+	else if (d_unhashed(lower_dentry))
Kmods SIG c540c3
+		rc = -EINVAL;
Kmods SIG c540c3
+	else
Kmods SIG c540c3
+		rc = vfs_unlink(lower_dir_inode, lower_dentry, NULL);
Kmods SIG c540c3
 	if (rc) {
Kmods SIG c540c3
 		printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc);
Kmods SIG c540c3
 		goto out_unlock;
Kmods SIG c540c3
@@ -142,10 +149,11 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
Kmods SIG c540c3
 	fsstack_copy_attr_times(dir, lower_dir_inode);
Kmods SIG c540c3
 	set_nlink(inode, ecryptfs_inode_to_lower(inode)->i_nlink);
Kmods SIG c540c3
 	inode->i_ctime = dir->i_ctime;
Kmods SIG c540c3
-	d_drop(dentry);
Kmods SIG c540c3
 out_unlock:
Kmods SIG c540c3
-	unlock_dir(lower_dir_dentry);
Kmods SIG c540c3
 	dput(lower_dentry);
Kmods SIG c540c3
+	inode_unlock(lower_dir_inode);
Kmods SIG c540c3
+	if (!rc)
Kmods SIG c540c3
+		d_drop(dentry);
Kmods SIG c540c3
 	return rc;
Kmods SIG c540c3
 }
Kmods SIG c540c3
 
Kmods SIG c540c3
@@ -512,22 +520,30 @@ static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry)
Kmods SIG c540c3
 {
Kmods SIG c540c3
 	struct dentry *lower_dentry;
Kmods SIG c540c3
 	struct dentry *lower_dir_dentry;
Kmods SIG c540c3
+	struct inode *lower_dir_inode;
Kmods SIG c540c3
 	int rc;
Kmods SIG c540c3
 
Kmods SIG c540c3
 	lower_dentry = ecryptfs_dentry_to_lower(dentry);
Kmods SIG c540c3
-	dget(dentry);
Kmods SIG c540c3
-	lower_dir_dentry = lock_parent(lower_dentry);
Kmods SIG c540c3
-	dget(lower_dentry);
Kmods SIG c540c3
-	rc = vfs_rmdir(d_inode(lower_dir_dentry), lower_dentry);
Kmods SIG c540c3
-	dput(lower_dentry);
Kmods SIG c540c3
-	if (!rc && d_really_is_positive(dentry))
Kmods SIG c540c3
+	lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent);
Kmods SIG c540c3
+	lower_dir_inode = d_inode(lower_dir_dentry);
Kmods SIG c540c3
+
Kmods SIG c540c3
+	inode_lock_nested(lower_dir_inode, I_MUTEX_PARENT);
Kmods SIG c540c3
+	dget(lower_dentry);	// don't even try to make the lower negative
Kmods SIG c540c3
+	if (lower_dentry->d_parent != lower_dir_dentry)
Kmods SIG c540c3
+		rc = -EINVAL;
Kmods SIG c540c3
+	else if (d_unhashed(lower_dentry))
Kmods SIG c540c3
+		rc = -EINVAL;
Kmods SIG c540c3
+	else
Kmods SIG c540c3
+		rc = vfs_rmdir(lower_dir_inode, lower_dentry);
Kmods SIG c540c3
+	if (!rc) {
Kmods SIG c540c3
 		clear_nlink(d_inode(dentry));
Kmods SIG c540c3
-	fsstack_copy_attr_times(dir, d_inode(lower_dir_dentry));
Kmods SIG c540c3
-	set_nlink(dir, d_inode(lower_dir_dentry)->i_nlink);
Kmods SIG c540c3
-	unlock_dir(lower_dir_dentry);
Kmods SIG c540c3
+		fsstack_copy_attr_times(dir, lower_dir_inode);
Kmods SIG c540c3
+		set_nlink(dir, lower_dir_inode->i_nlink);
Kmods SIG c540c3
+	}
Kmods SIG c540c3
+	dput(lower_dentry);
Kmods SIG c540c3
+	inode_unlock(lower_dir_inode);
Kmods SIG c540c3
 	if (!rc)
Kmods SIG c540c3
 		d_drop(dentry);
Kmods SIG c540c3
-	dput(dentry);
Kmods SIG c540c3
 	return rc;
Kmods SIG c540c3
 }
Kmods SIG c540c3
 
Kmods SIG c540c3
@@ -565,20 +581,22 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
Kmods SIG c540c3
 	struct dentry *lower_new_dentry;
Kmods SIG c540c3
 	struct dentry *lower_old_dir_dentry;
Kmods SIG c540c3
 	struct dentry *lower_new_dir_dentry;
Kmods SIG c540c3
-	struct dentry *trap = NULL;
Kmods SIG c540c3
+	struct dentry *trap;
Kmods SIG c540c3
 	struct inode *target_inode;
Kmods SIG c540c3
 
Kmods SIG c540c3
 	if (flags)
Kmods SIG c540c3
 		return -EINVAL;
Kmods SIG c540c3
 
Kmods SIG c540c3
+	lower_old_dir_dentry = ecryptfs_dentry_to_lower(old_dentry->d_parent);
Kmods SIG c540c3
+	lower_new_dir_dentry = ecryptfs_dentry_to_lower(new_dentry->d_parent);
Kmods SIG c540c3
+
Kmods SIG c540c3
 	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
Kmods SIG c540c3
 	lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry);
Kmods SIG c540c3
-	dget(lower_old_dentry);
Kmods SIG c540c3
-	dget(lower_new_dentry);
Kmods SIG c540c3
-	lower_old_dir_dentry = dget_parent(lower_old_dentry);
Kmods SIG c540c3
-	lower_new_dir_dentry = dget_parent(lower_new_dentry);
Kmods SIG c540c3
+
Kmods SIG c540c3
 	target_inode = d_inode(new_dentry);
Kmods SIG c540c3
+
Kmods SIG c540c3
 	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
Kmods SIG c540c3
+	dget(lower_new_dentry);
Kmods SIG c540c3
 	rc = -EINVAL;
Kmods SIG c540c3
 	if (lower_old_dentry->d_parent != lower_old_dir_dentry)
Kmods SIG c540c3
 		goto out_lock;
Kmods SIG c540c3
@@ -606,11 +624,8 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
Kmods SIG c540c3
 	if (new_dir != old_dir)
Kmods SIG c540c3
 		fsstack_copy_attr_all(old_dir, d_inode(lower_old_dir_dentry));
Kmods SIG c540c3
 out_lock:
Kmods SIG c540c3
-	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
Kmods SIG c540c3
-	dput(lower_new_dir_dentry);
Kmods SIG c540c3
-	dput(lower_old_dir_dentry);
Kmods SIG c540c3
 	dput(lower_new_dentry);
Kmods SIG c540c3
-	dput(lower_old_dentry);
Kmods SIG c540c3
+	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
Kmods SIG c540c3
 	return rc;
Kmods SIG c540c3
 }
Kmods SIG c540c3
 
Kmods SIG c540c3
-- 
Kmods SIG c540c3
2.31.1
Kmods SIG c540c3