fanotify: Track permission event state

Track whether permission event got already reported to userspace and
whether userspace already answered to the permission event. Protect
stores to this field together with updates to ->response field by
group->notification_lock. This will allow aborting wait for reply to
permission event from userspace.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>
This commit is contained in:
Jan Kara 2019-01-08 14:02:44 +01:00
parent ca6f86998d
commit 40873284d7
3 changed files with 40 additions and 11 deletions

View file

@ -85,7 +85,8 @@ static int fanotify_get_response(struct fsnotify_group *group,
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
wait_event(group->fanotify_data.access_waitq, event->response);
wait_event(group->fanotify_data.access_waitq,
event->state == FAN_EVENT_ANSWERED);
/* userspace responded, convert to something usable */
switch (event->response & ~FAN_AUDIT) {
@ -101,8 +102,6 @@ static int fanotify_get_response(struct fsnotify_group *group,
if (event->response & FAN_AUDIT)
audit_fanotify(event->response & ~FAN_AUDIT);
event->response = 0;
pr_debug("%s: group=%p event=%p about to return ret=%d\n", __func__,
group, event, ret);
@ -275,6 +274,7 @@ struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
goto out;
event = &pevent->fae;
pevent->response = 0;
pevent->state = FAN_EVENT_INIT;
goto init;
}
event = kmem_cache_alloc(fanotify_event_cachep, gfp);

View file

@ -8,6 +8,13 @@ extern struct kmem_cache *fanotify_mark_cache;
extern struct kmem_cache *fanotify_event_cachep;
extern struct kmem_cache *fanotify_perm_event_cachep;
/* Possible states of the permission event */
enum {
FAN_EVENT_INIT,
FAN_EVENT_REPORTED,
FAN_EVENT_ANSWERED
};
/*
* 3 dwords are sufficient for most local fs (64bit ino, 32bit generation).
* For 32bit arch, fid increases the size of fanotify_event by 12 bytes and
@ -109,7 +116,8 @@ static inline void *fanotify_event_fh(struct fanotify_event *event)
*/
struct fanotify_perm_event {
struct fanotify_event fae;
int response; /* userspace answer to question */
unsigned short response; /* userspace answer to the event */
unsigned short state; /* state of the event */
int fd; /* fd we passed to userspace for this event */
};

View file

@ -64,7 +64,8 @@ static int fanotify_event_info_len(struct fanotify_event *event)
/*
* Get an fsnotify notification event if one exists and is small
* enough to fit in "count". Return an error pointer if the count
* is not large enough.
* is not large enough. When permission event is dequeued, its state is
* updated accordingly.
*/
static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
size_t count)
@ -88,6 +89,8 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
goto out;
}
fsn_event = fsnotify_remove_first_event(group);
if (fanotify_is_perm_event(FANOTIFY_E(fsn_event)->mask))
FANOTIFY_PE(fsn_event)->state = FAN_EVENT_REPORTED;
out:
spin_unlock(&group->notification_lock);
return fsn_event;
@ -135,6 +138,21 @@ static int create_fd(struct fsnotify_group *group,
return client_fd;
}
/*
* Finish processing of permission event by setting it to ANSWERED state and
* drop group->notification_lock.
*/
static void finish_permission_event(struct fsnotify_group *group,
struct fanotify_perm_event *event,
unsigned int response)
__releases(&group->notification_lock)
{
assert_spin_locked(&group->notification_lock);
event->response = response;
event->state = FAN_EVENT_ANSWERED;
spin_unlock(&group->notification_lock);
}
static int process_access_response(struct fsnotify_group *group,
struct fanotify_response *response_struct)
{
@ -170,8 +188,7 @@ static int process_access_response(struct fsnotify_group *group,
continue;
list_del_init(&event->fae.fse.list);
event->response = response;
spin_unlock(&group->notification_lock);
finish_permission_event(group, event, response);
wake_up(&group->fanotify_data.access_waitq);
return 0;
}
@ -354,7 +371,9 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
fsnotify_destroy_event(group, kevent);
} else {
if (ret <= 0) {
FANOTIFY_PE(kevent)->response = FAN_DENY;
spin_lock(&group->notification_lock);
finish_permission_event(group,
FANOTIFY_PE(kevent), FAN_DENY);
wake_up(&group->fanotify_data.access_waitq);
} else {
spin_lock(&group->notification_lock);
@ -423,7 +442,8 @@ static int fanotify_release(struct inode *ignored, struct file *file)
event = list_first_entry(&group->fanotify_data.access_list,
struct fanotify_perm_event, fae.fse.list);
list_del_init(&event->fae.fse.list);
event->response = FAN_ALLOW;
finish_permission_event(group, event, FAN_ALLOW);
spin_lock(&group->notification_lock);
}
/*
@ -436,10 +456,11 @@ static int fanotify_release(struct inode *ignored, struct file *file)
if (!(FANOTIFY_E(fsn_event)->mask & FANOTIFY_PERM_EVENTS)) {
spin_unlock(&group->notification_lock);
fsnotify_destroy_event(group, fsn_event);
spin_lock(&group->notification_lock);
} else {
FANOTIFY_PE(fsn_event)->response = FAN_ALLOW;
finish_permission_event(group, FANOTIFY_PE(fsn_event),
FAN_ALLOW);
}
spin_lock(&group->notification_lock);
}
spin_unlock(&group->notification_lock);