diff options
| author | Jan Kara <jack@suse.cz> | 2016-12-12 10:08:41 -0500 |
|---|---|---|
| committer | Jan Kara <jack@suse.cz> | 2016-12-13 06:57:52 -0500 |
| commit | 5716863e0f8251d3360d4cbfc0e44e08007075df (patch) | |
| tree | 4f6ebe911e257e49154b4741c117e2354d77ed4a | |
| parent | b46dc033818d3293ecc49dc258e2efb603c80bd7 (diff) | |
fsnotify: Fix possible use-after-free in inode iteration on umount
fsnotify_unmount_inodes() plays complex tricks to pin next inode in the
sb->s_inodes list when iterating over all inodes. Furthermore the code has a
bug that if the current inode is the last on i_sb_list that does not have e.g.
I_FREEING set, then we leave next_i pointing to inode which may get removed
from the i_sb_list once we drop s_inode_list_lock thus resulting in
use-after-free issues (usually manifesting as infinite looping in
fsnotify_unmount_inodes()).
Fix the problem by keeping current inode pinned somewhat longer. Then we can
make the code much simpler and standard.
CC: stable@vger.kernel.org
Signed-off-by: Jan Kara <jack@suse.cz>
| -rw-r--r-- | fs/notify/inode_mark.c | 45 |
1 files changed, 9 insertions, 36 deletions
diff --git a/fs/notify/inode_mark.c b/fs/notify/inode_mark.c index 741077deef3b..a3645249f7ec 100644 --- a/fs/notify/inode_mark.c +++ b/fs/notify/inode_mark.c | |||
| @@ -150,12 +150,10 @@ int fsnotify_add_inode_mark(struct fsnotify_mark *mark, | |||
| 150 | */ | 150 | */ |
| 151 | void fsnotify_unmount_inodes(struct super_block *sb) | 151 | void fsnotify_unmount_inodes(struct super_block *sb) |
| 152 | { | 152 | { |
| 153 | struct inode *inode, *next_i, *need_iput = NULL; | 153 | struct inode *inode, *iput_inode = NULL; |
| 154 | 154 | ||
| 155 | spin_lock(&sb->s_inode_list_lock); | 155 | spin_lock(&sb->s_inode_list_lock); |
| 156 | list_for_each_entry_safe(inode, next_i, &sb->s_inodes, i_sb_list) { | 156 | list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { |
| 157 | struct inode *need_iput_tmp; | ||
| 158 | |||
| 159 | /* | 157 | /* |
| 160 | * We cannot __iget() an inode in state I_FREEING, | 158 | * We cannot __iget() an inode in state I_FREEING, |
| 161 | * I_WILL_FREE, or I_NEW which is fine because by that point | 159 | * I_WILL_FREE, or I_NEW which is fine because by that point |
| @@ -178,49 +176,24 @@ void fsnotify_unmount_inodes(struct super_block *sb) | |||
| 178 | continue; | 176 | continue; |
| 179 | } | 177 | } |
| 180 | 178 | ||
| 181 | need_iput_tmp = need_iput; | 179 | __iget(inode); |
| 182 | need_iput = NULL; | ||
| 183 | |||
| 184 | /* In case fsnotify_inode_delete() drops a reference. */ | ||
| 185 | if (inode != need_iput_tmp) | ||
| 186 | __iget(inode); | ||
| 187 | else | ||
| 188 | need_iput_tmp = NULL; | ||
| 189 | spin_unlock(&inode->i_lock); | 180 | spin_unlock(&inode->i_lock); |
| 190 | |||
| 191 | /* In case the dropping of a reference would nuke next_i. */ | ||
| 192 | while (&next_i->i_sb_list != &sb->s_inodes) { | ||
| 193 | spin_lock(&next_i->i_lock); | ||
| 194 | if (!(next_i->i_state & (I_FREEING | I_WILL_FREE)) && | ||
| 195 | atomic_read(&next_i->i_count)) { | ||
| 196 | __iget(next_i); | ||
| 197 | need_iput = next_i; | ||
| 198 | spin_unlock(&next_i->i_lock); | ||
| 199 | break; | ||
| 200 | } | ||
| 201 | spin_unlock(&next_i->i_lock); | ||
| 202 | next_i = list_next_entry(next_i, i_sb_list); | ||
| 203 | } | ||
| 204 | |||
| 205 | /* | ||
| 206 | * We can safely drop s_inode_list_lock here because either | ||
| 207 | * we actually hold references on both inode and next_i or | ||
| 208 | * end of list. Also no new inodes will be added since the | ||
| 209 | * umount has begun. | ||
| 210 | */ | ||
| 211 | spin_unlock(&sb->s_inode_list_lock); | 181 | spin_unlock(&sb->s_inode_list_lock); |
| 212 | 182 | ||
| 213 | if (need_iput_tmp) | 183 | if (iput_inode) |
| 214 | iput(need_iput_tmp); | 184 | iput(iput_inode); |
| 215 | 185 | ||
| 216 | /* for each watch, send FS_UNMOUNT and then remove it */ | 186 | /* for each watch, send FS_UNMOUNT and then remove it */ |
| 217 | fsnotify(inode, FS_UNMOUNT, inode, FSNOTIFY_EVENT_INODE, NULL, 0); | 187 | fsnotify(inode, FS_UNMOUNT, inode, FSNOTIFY_EVENT_INODE, NULL, 0); |
| 218 | 188 | ||
| 219 | fsnotify_inode_delete(inode); | 189 | fsnotify_inode_delete(inode); |
| 220 | 190 | ||
| 221 | iput(inode); | 191 | iput_inode = inode; |
| 222 | 192 | ||
| 223 | spin_lock(&sb->s_inode_list_lock); | 193 | spin_lock(&sb->s_inode_list_lock); |
| 224 | } | 194 | } |
| 225 | spin_unlock(&sb->s_inode_list_lock); | 195 | spin_unlock(&sb->s_inode_list_lock); |
| 196 | |||
| 197 | if (iput_inode) | ||
| 198 | iput(iput_inode); | ||
| 226 | } | 199 | } |
