mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	fanotify: fix double free of pending permission events
Commit8581679424("fanotify: Fix use after free for permission events") introduced a double free issue for permission events which are pending in group's notification queue while group is being destroyed. These events are freed from fanotify_handle_event() but they are not removed from groups notification queue and thus they get freed again from fsnotify_flush_notify(). Fix the problem by removing permission events from notification queue before freeing them if we skip processing access response. Also expand comments in fanotify_release() to explain group shutdown in detail. Fixes:8581679424Signed-off-by: Jan Kara <jack@suse.cz> Reported-by: Douglas Leeder <douglas.leeder@sophos.com> Tested-by: Douglas Leeder <douglas.leeder@sophos.com> Reported-by: Heinrich Schuchard <xypron.glpk@gmx.de> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
		
							parent
							
								
									8ba8fa9170
								
							
						
					
					
						commit
						5838d4442b
					
				
					 4 changed files with 39 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -70,8 +70,15 @@ static int fanotify_get_response(struct fsnotify_group *group,
 | 
			
		|||
	wait_event(group->fanotify_data.access_waitq, event->response ||
 | 
			
		||||
				atomic_read(&group->fanotify_data.bypass_perm));
 | 
			
		||||
 | 
			
		||||
	if (!event->response) /* bypass_perm set */
 | 
			
		||||
	if (!event->response) {	/* bypass_perm set */
 | 
			
		||||
		/*
 | 
			
		||||
		 * Event was canceled because group is being destroyed. Remove
 | 
			
		||||
		 * it from group's event list because we are responsible for
 | 
			
		||||
		 * freeing the permission event.
 | 
			
		||||
		 */
 | 
			
		||||
		fsnotify_remove_event(group, &event->fae.fse);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* userspace responded, convert to something usable */
 | 
			
		||||
	switch (event->response) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -359,6 +359,11 @@ static int fanotify_release(struct inode *ignored, struct file *file)
 | 
			
		|||
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
 | 
			
		||||
	struct fanotify_perm_event_info *event, *next;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * There may be still new events arriving in the notification queue
 | 
			
		||||
	 * but since userspace cannot use fanotify fd anymore, no event can
 | 
			
		||||
	 * enter or leave access_list by now.
 | 
			
		||||
	 */
 | 
			
		||||
	spin_lock(&group->fanotify_data.access_lock);
 | 
			
		||||
 | 
			
		||||
	atomic_inc(&group->fanotify_data.bypass_perm);
 | 
			
		||||
| 
						 | 
				
			
			@ -373,6 +378,13 @@ static int fanotify_release(struct inode *ignored, struct file *file)
 | 
			
		|||
	}
 | 
			
		||||
	spin_unlock(&group->fanotify_data.access_lock);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Since bypass_perm is set, newly queued events will not wait for
 | 
			
		||||
	 * access response. Wake up the already sleeping ones now.
 | 
			
		||||
	 * synchronize_srcu() in fsnotify_destroy_group() will wait for all
 | 
			
		||||
	 * processes sleeping in fanotify_handle_event() waiting for access
 | 
			
		||||
	 * response and thus also for all permission events to be freed.
 | 
			
		||||
	 */
 | 
			
		||||
	wake_up(&group->fanotify_data.access_waitq);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,8 @@ void fsnotify_destroy_event(struct fsnotify_group *group,
 | 
			
		|||
	/* Overflow events are per-group and we don't want to free them */
 | 
			
		||||
	if (!event || event->mask == FS_Q_OVERFLOW)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* If the event is still queued, we have a problem... */
 | 
			
		||||
	WARN_ON(!list_empty(&event->list));
 | 
			
		||||
	group->ops->free_event(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +125,21 @@ int fsnotify_add_event(struct fsnotify_group *group,
 | 
			
		|||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Remove @event from group's notification queue. It is the responsibility of
 | 
			
		||||
 * the caller to destroy the event.
 | 
			
		||||
 */
 | 
			
		||||
void fsnotify_remove_event(struct fsnotify_group *group,
 | 
			
		||||
			   struct fsnotify_event *event)
 | 
			
		||||
{
 | 
			
		||||
	mutex_lock(&group->notification_mutex);
 | 
			
		||||
	if (!list_empty(&event->list)) {
 | 
			
		||||
		list_del_init(&event->list);
 | 
			
		||||
		group->q_len--;
 | 
			
		||||
	}
 | 
			
		||||
	mutex_unlock(&group->notification_mutex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Remove and return the first event from the notification list.  It is the
 | 
			
		||||
 * responsibility of the caller to destroy the obtained event
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -326,6 +326,8 @@ extern int fsnotify_add_event(struct fsnotify_group *group,
 | 
			
		|||
			      struct fsnotify_event *event,
 | 
			
		||||
			      int (*merge)(struct list_head *,
 | 
			
		||||
					   struct fsnotify_event *));
 | 
			
		||||
/* Remove passed event from groups notification queue */
 | 
			
		||||
extern void fsnotify_remove_event(struct fsnotify_group *group, struct fsnotify_event *event);
 | 
			
		||||
/* true if the group notification queue is empty */
 | 
			
		||||
extern bool fsnotify_notify_queue_is_empty(struct fsnotify_group *group);
 | 
			
		||||
/* return, but do not dequeue the first event on the notification queue */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue