Thread (2 messages) 2 messages, 2 authors, 2016-09-08

Re: [PATCH] fanotify: fix race between fanotify_release() and fanotify_get_response()

From: Jan Kara <jack@suse.cz>
Date: 2016-09-08 10:55:35
Also in: lkml

On Wed 24-08-16 11:55:39, Miklos Szeredi wrote:
List corruption was reported with a fanotify stress test.

The bug turned out to be due to fsnotify_remove_event() being called on an
event on the fanotify_data.access_list and protected by
fanotify_data.access_lock instead of notification_mutex.  This resulted in
list_del_init() being run concurrently on the same list entry.

This was introduced by commit 09e5f14e57c7 ("fanotify: on group destroy
allow all waiters to bypass permission check") which made
fanotify_get_response() flush out events when bypass_perm was set.  The
flush doesn't normally happen, since the wake_up() is called after the
access_list was cleaned in fsnotify_release().  But the two are not
synchronized, the fanotify_get_response() could still be processing a
previous wakeup by the time bypass_perm is true.  This was seen in the
crashdumps in the report.
Thanks for the analysis and the patch! I agree there's a bug you describe,
I just somewhat disagree with the solution.
This bug can be solved multiple ways, maybe the simplest is moving the
bypass_perm setting after the list has been processed.

In theory there's also a memory ordering problem here.  atomic_inc() in
itself doesn't imply a memory barrier, and spin_unlock() is a semi
permeable barrier, so we need an explicit memory barrier so that the
condition is precieved after the list is cleared.
Well, the culprit of the problem seems to be that fanotify_get_response()
does not use proper function to remove the event from the list in all the
cases. The event can be in two states:

1) User didn't yet read the even at the time fanotify_release() is closed -
note that a group can still receive new events queued while
fanotify_release() is running until fsnotify_destroy_group() kills all the
marks and that is the main reason why we have that bypass_perm thing to
avoid blocking new processes in fanotify_get_response() because they could
otherwise miss a wakeup and hang there indefinitely. In this state calling
fsnotify_remove_event() is correct and that's what commit 5838d4442bd5
(fanotify: fix double free of pending permission events) had in mind.

2) User has read the event (thus the permission even was moved to
access_list) but didn't write the response yet. In this state calling
fsnotify_remove_event() from fanotify_get_response() is just wrong - as you
noted it uses the wrong lock to protect the list removal but it also
wrongly updates group->q_len. In this situation we should be calling
dequeue_event().

The problem is that there's no easy way to distinguishing these two cases
in fanotify_get_response(). We could flag this somehow inside the event
structure but I think it's cleaner to remove all permission events in
fanotify_release() (the same way we already handle the permission events on
access_list). I'll send a patch shortly.
Similarly we need barriers for the case when event->response is set
(i.e. non zero): fsnotify_destroy_event() might destroy the event while
it's still on the access_list, since nothing guarantees that the storing
the response value in event->response will be preceived after the list
manipulation.  So add the necessary barriers there as well.

PS not sure why bypass_perm is an atomic_t, it could just as well be a
boolean flag.
Agreed. I was just never bothered enough to fix this.
PPS all this subtlety could be removed if the waitq was per-event, which
would also allow better performance.
I'm not sure how much this problem would be helped by a per-event
waitqueue.  Also it would add a significant memory cost to permission
events and I prefer them small as there can be a lot of them queued...

								Honza

-- 
Jan Kara [off-list ref]
SUSE Labs, CR
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help