aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2016-12-12 10:08:41 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-01-09 02:32:22 -0500
commitd06485e0fcf58a88daadcbe119a7d433cdaad8e6 (patch)
tree06722f54f2b8a7d3ba84ada46805dc1d7d5ac22e /fs
parent3f618a0b872fea38c7d1d1f79eda40f88c6466c2 (diff)
fsnotify: Fix possible use-after-free in inode iteration on umount
commit 5716863e0f8251d3360d4cbfc0e44e08007075df upstream. 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. Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/notify/inode_mark.c45
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 */
151void fsnotify_unmount_inodes(struct super_block *sb) 151void 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}