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

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