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; }