diff options
author | Jan Kara <jack@suse.cz> | 2019-04-24 12:39:57 -0400 |
---|---|---|
committer | Jan Kara <jack@suse.cz> | 2019-04-28 16:14:50 -0400 |
commit | b1da6a51871c6929dced1a7fad81990988b36ed6 (patch) | |
tree | 2c487de5f9e6369a9ee459e93d10dd37f094e945 | |
parent | ba25b50d582ff6c6021eee80824134aeb9ab8785 (diff) |
fsnotify: Fix NULL ptr deref in fanotify_get_fsid()
fanotify_get_fsid() is reading mark->connector->fsid under srcu. It can
happen that it sees mark not fully initialized or mark that is already
detached from the object list. In these cases mark->connector
can be NULL leading to NULL ptr dereference. Fix the problem by
being careful when reading mark->connector and check it for being NULL.
Also use WRITE_ONCE when writing the mark just to prevent compiler from
doing something stupid.
Reported-by: syzbot+15927486a4f1bfcbaf91@syzkaller.appspotmail.com
Fixes: 77115225acc6 ("fanotify: cache fsid in fsnotify_mark_connector")
Signed-off-by: Jan Kara <jack@suse.cz>
-rw-r--r-- | fs/notify/fanotify/fanotify.c | 14 | ||||
-rw-r--r-- | fs/notify/mark.c | 12 |
2 files changed, 18 insertions, 8 deletions
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index 6b9c27548997..63c6bb1f8c4d 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c | |||
@@ -346,10 +346,16 @@ static __kernel_fsid_t fanotify_get_fsid(struct fsnotify_iter_info *iter_info) | |||
346 | __kernel_fsid_t fsid = {}; | 346 | __kernel_fsid_t fsid = {}; |
347 | 347 | ||
348 | fsnotify_foreach_obj_type(type) { | 348 | fsnotify_foreach_obj_type(type) { |
349 | struct fsnotify_mark_connector *conn; | ||
350 | |||
349 | if (!fsnotify_iter_should_report_type(iter_info, type)) | 351 | if (!fsnotify_iter_should_report_type(iter_info, type)) |
350 | continue; | 352 | continue; |
351 | 353 | ||
352 | fsid = iter_info->marks[type]->connector->fsid; | 354 | conn = READ_ONCE(iter_info->marks[type]->connector); |
355 | /* Mark is just getting destroyed or created? */ | ||
356 | if (!conn) | ||
357 | continue; | ||
358 | fsid = conn->fsid; | ||
353 | if (WARN_ON_ONCE(!fsid.val[0] && !fsid.val[1])) | 359 | if (WARN_ON_ONCE(!fsid.val[0] && !fsid.val[1])) |
354 | continue; | 360 | continue; |
355 | return fsid; | 361 | return fsid; |
@@ -408,8 +414,12 @@ static int fanotify_handle_event(struct fsnotify_group *group, | |||
408 | return 0; | 414 | return 0; |
409 | } | 415 | } |
410 | 416 | ||
411 | if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) | 417 | if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) { |
412 | fsid = fanotify_get_fsid(iter_info); | 418 | fsid = fanotify_get_fsid(iter_info); |
419 | /* Racing with mark destruction or creation? */ | ||
420 | if (!fsid.val[0] && !fsid.val[1]) | ||
421 | return 0; | ||
422 | } | ||
413 | 423 | ||
414 | event = fanotify_alloc_event(group, inode, mask, data, data_type, | 424 | event = fanotify_alloc_event(group, inode, mask, data, data_type, |
415 | &fsid); | 425 | &fsid); |
diff --git a/fs/notify/mark.c b/fs/notify/mark.c index d593d4269561..22acb0a79b53 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c | |||
@@ -239,13 +239,13 @@ static void fsnotify_drop_object(unsigned int type, void *objp) | |||
239 | 239 | ||
240 | void fsnotify_put_mark(struct fsnotify_mark *mark) | 240 | void fsnotify_put_mark(struct fsnotify_mark *mark) |
241 | { | 241 | { |
242 | struct fsnotify_mark_connector *conn; | 242 | struct fsnotify_mark_connector *conn = READ_ONCE(mark->connector); |
243 | void *objp = NULL; | 243 | void *objp = NULL; |
244 | unsigned int type = FSNOTIFY_OBJ_TYPE_DETACHED; | 244 | unsigned int type = FSNOTIFY_OBJ_TYPE_DETACHED; |
245 | bool free_conn = false; | 245 | bool free_conn = false; |
246 | 246 | ||
247 | /* Catch marks that were actually never attached to object */ | 247 | /* Catch marks that were actually never attached to object */ |
248 | if (!mark->connector) { | 248 | if (!conn) { |
249 | if (refcount_dec_and_test(&mark->refcnt)) | 249 | if (refcount_dec_and_test(&mark->refcnt)) |
250 | fsnotify_final_mark_destroy(mark); | 250 | fsnotify_final_mark_destroy(mark); |
251 | return; | 251 | return; |
@@ -255,10 +255,9 @@ void fsnotify_put_mark(struct fsnotify_mark *mark) | |||
255 | * We have to be careful so that traversals of obj_list under lock can | 255 | * We have to be careful so that traversals of obj_list under lock can |
256 | * safely grab mark reference. | 256 | * safely grab mark reference. |
257 | */ | 257 | */ |
258 | if (!refcount_dec_and_lock(&mark->refcnt, &mark->connector->lock)) | 258 | if (!refcount_dec_and_lock(&mark->refcnt, &conn->lock)) |
259 | return; | 259 | return; |
260 | 260 | ||
261 | conn = mark->connector; | ||
262 | hlist_del_init_rcu(&mark->obj_list); | 261 | hlist_del_init_rcu(&mark->obj_list); |
263 | if (hlist_empty(&conn->list)) { | 262 | if (hlist_empty(&conn->list)) { |
264 | objp = fsnotify_detach_connector_from_object(conn, &type); | 263 | objp = fsnotify_detach_connector_from_object(conn, &type); |
@@ -266,7 +265,7 @@ void fsnotify_put_mark(struct fsnotify_mark *mark) | |||
266 | } else { | 265 | } else { |
267 | __fsnotify_recalc_mask(conn); | 266 | __fsnotify_recalc_mask(conn); |
268 | } | 267 | } |
269 | mark->connector = NULL; | 268 | WRITE_ONCE(mark->connector, NULL); |
270 | spin_unlock(&conn->lock); | 269 | spin_unlock(&conn->lock); |
271 | 270 | ||
272 | fsnotify_drop_object(type, objp); | 271 | fsnotify_drop_object(type, objp); |
@@ -620,7 +619,7 @@ restart: | |||
620 | /* mark should be the last entry. last is the current last entry */ | 619 | /* mark should be the last entry. last is the current last entry */ |
621 | hlist_add_behind_rcu(&mark->obj_list, &last->obj_list); | 620 | hlist_add_behind_rcu(&mark->obj_list, &last->obj_list); |
622 | added: | 621 | added: |
623 | mark->connector = conn; | 622 | WRITE_ONCE(mark->connector, conn); |
624 | out_err: | 623 | out_err: |
625 | spin_unlock(&conn->lock); | 624 | spin_unlock(&conn->lock); |
626 | spin_unlock(&mark->lock); | 625 | spin_unlock(&mark->lock); |
@@ -808,6 +807,7 @@ void fsnotify_init_mark(struct fsnotify_mark *mark, | |||
808 | refcount_set(&mark->refcnt, 1); | 807 | refcount_set(&mark->refcnt, 1); |
809 | fsnotify_get_group(group); | 808 | fsnotify_get_group(group); |
810 | mark->group = group; | 809 | mark->group = group; |
810 | WRITE_ONCE(mark->connector, NULL); | ||
811 | } | 811 | } |
812 | 812 | ||
813 | /* | 813 | /* |