Vadim Egorov: ext3 link/unlink race

Linux Kernel Mailing List <[email protected]> Fri, 12 May 2006 18:59:49 GMT
Newsgroups gmane.linux.kernel.commits.2-4
Message-ID <[email protected]>
commit f41e0ce901260d3d1ae5bd8bae34266891b4a65d
tree bd9f3b973d4c84458ec866641009e35beeeb2225
parent 925c7ce0a2d9a676cd8e4a2baf411b23cf6762d6
author Marcelo Tosatti <[email protected]> Fri, 12 May 2006 21:02:33 -0300
committer Marcelo Tosatti <[email protected]> Fri, 12 May 2006 21:02:33 -0300

Vadim Egorov: ext3 link/unlink race

I found this issue with 2.4.27 kernel but I believe that other versions
are affected too.

The problem happens when link and unlink are invoked simultaneously on
the same inode on ext3 filesystem. In this case ext3_unlink may
decrement i_nlink to 0 and put this inode into the in-memory orphan
list, while ext3_link will increment i_nlink back to 1 having the inode
in the orphan list.

Thus the system ends up having an inode with i_nlink == 1 in the orphan
list.

When this inode gets unused later it the memory might get released to
the free pool and then be used for some other purpose, most likely some
other inode.

From this point on any operation on the orphan list may result in
modification of the list_head that could alredy be used to store some
other date.

 fs/namei.c |   26 ++++++++++++++------------
 1 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index da80d85..48bd26c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1479,7 +1479,7 @@ int vfs_unlink(struct inode *dir, struct
 {
 	int error;
 
-	down(&dir->i_zombie);
+	double_down(&dir->i_zombie, &dentry->d_inode->i_zombie);
 	error = may_delete(dir, dentry, 0);
 	if (!error) {
 		error = -EPERM;
@@ -1491,14 +1491,14 @@ int vfs_unlink(struct inode *dir, struct
 				lock_kernel();
 				error = dir->i_op->unlink(dir, dentry);
 				unlock_kernel();
-				if (!error)
-					d_delete(dentry);
 			}
 		}
 	}
-	up(&dir->i_zombie);
-	if (!error)
+	double_up(&dir->i_zombie, &dentry->d_inode->i_zombie);
+	if (!error) {
+		d_delete(dentry);
 		inode_dir_notify(dir, DN_DELETE);
+	}
 	return error;
 }
 
@@ -1607,18 +1607,19 @@ int vfs_link(struct dentry *old_dentry, 
 	struct inode *inode;
 	int error;
 
-	down(&dir->i_zombie);
 	error = -ENOENT;
 	inode = old_dentry->d_inode;
 	if (!inode)
-		goto exit_lock;
-
-	error = may_create(dir, new_dentry);
-	if (error)
-		goto exit_lock;
+		goto exit;
 
 	error = -EXDEV;
 	if (dir->i_dev != inode->i_dev)
+		goto exit;
+
+	double_down(&dir->i_zombie, &old_dentry->d_inode->i_zombie);
+
+	error = may_create(dir, new_dentry);
+	if (error)
 		goto exit_lock;
 
 	/*
@@ -1636,9 +1637,10 @@ int vfs_link(struct dentry *old_dentry, 
 	unlock_kernel();
 
 exit_lock:
-	up(&dir->i_zombie);
+	double_up(&dir->i_zombie, &old_dentry->d_inode->i_zombie);
 	if (!error)
 		inode_dir_notify(dir, DN_CREATE);
+exit:
 	return error;
 }