diff options
| author | Theodore Ts'o <tytso@mit.edu> | 2011-07-30 12:34:19 -0400 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2011-07-30 12:34:19 -0400 |
| commit | d59729f4e794f814b25ccd2aebfbe606242c4544 (patch) | |
| tree | 96b7cfef8124c23b6a17ded73fddffc567d6f2a1 | |
| parent | 29ae07b702cb77dbc24b0843f15ee8cf8a642311 (diff) | |
ext4: fix races in ext4_sync_parent()
Fix problems if fsync() races against a rename of a parent directory
as pointed out by Al Viro in his own inimitable way:
>While we are at it, could somebody please explain what the hell is ext4
>doing in
>static int ext4_sync_parent(struct inode *inode)
>{
> struct writeback_control wbc;
> struct dentry *dentry = NULL;
> int ret = 0;
>
> while (inode && ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) {
> ext4_clear_inode_state(inode, EXT4_STATE_NEWENTRY);
> dentry = list_entry(inode->i_dentry.next,
> struct dentry, d_alias);
> if (!dentry || !dentry->d_parent || !dentry->d_parent->d_inode)
> break;
> inode = dentry->d_parent->d_inode;
> ret = sync_mapping_buffers(inode->i_mapping);
> ...
>Note that dentry obviously can't be NULL there. dentry->d_parent is never
>NULL. And dentry->d_parent would better not be negative, for crying out
>loud! What's worse, there's no guarantees that dentry->d_parent will
>remain our parent over that sync_mapping_buffers() *and* that inode won't
>just be freed under us (after rename() and memory pressure leading to
>eviction of what used to be our dentry->d_parent)......
Reported-by: Al Viro <viro@ZenIV.linux.org.uk>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
| -rw-r--r-- | fs/ext4/fsync.c | 26 |
1 files changed, 21 insertions, 5 deletions
diff --git a/fs/ext4/fsync.c b/fs/ext4/fsync.c index ce66d2fe826c..f9dbe33cde5e 100644 --- a/fs/ext4/fsync.c +++ b/fs/ext4/fsync.c | |||
| @@ -129,15 +129,30 @@ static int ext4_sync_parent(struct inode *inode) | |||
| 129 | { | 129 | { |
| 130 | struct writeback_control wbc; | 130 | struct writeback_control wbc; |
| 131 | struct dentry *dentry = NULL; | 131 | struct dentry *dentry = NULL; |
| 132 | struct inode *next; | ||
| 132 | int ret = 0; | 133 | int ret = 0; |
| 133 | 134 | ||
| 134 | while (inode && ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) { | 135 | if (!ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) |
| 136 | return 0; | ||
| 137 | inode = igrab(inode); | ||
| 138 | while (ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) { | ||
| 135 | ext4_clear_inode_state(inode, EXT4_STATE_NEWENTRY); | 139 | ext4_clear_inode_state(inode, EXT4_STATE_NEWENTRY); |
| 136 | dentry = list_entry(inode->i_dentry.next, | 140 | dentry = NULL; |
| 137 | struct dentry, d_alias); | 141 | spin_lock(&inode->i_lock); |
| 138 | if (!dentry || !dentry->d_parent || !dentry->d_parent->d_inode) | 142 | if (!list_empty(&inode->i_dentry)) { |
| 143 | dentry = list_first_entry(&inode->i_dentry, | ||
| 144 | struct dentry, d_alias); | ||
| 145 | dget(dentry); | ||
| 146 | } | ||
| 147 | spin_unlock(&inode->i_lock); | ||
| 148 | if (!dentry) | ||
| 149 | break; | ||
| 150 | next = igrab(dentry->d_parent->d_inode); | ||
| 151 | dput(dentry); | ||
| 152 | if (!next) | ||
| 139 | break; | 153 | break; |
| 140 | inode = dentry->d_parent->d_inode; | 154 | iput(inode); |
| 155 | inode = next; | ||
| 141 | ret = sync_mapping_buffers(inode->i_mapping); | 156 | ret = sync_mapping_buffers(inode->i_mapping); |
| 142 | if (ret) | 157 | if (ret) |
| 143 | break; | 158 | break; |
| @@ -148,6 +163,7 @@ static int ext4_sync_parent(struct inode *inode) | |||
| 148 | if (ret) | 163 | if (ret) |
| 149 | break; | 164 | break; |
| 150 | } | 165 | } |
| 166 | iput(inode); | ||
| 151 | return ret; | 167 | return ret; |
| 152 | } | 168 | } |
| 153 | 169 | ||
