fanotify: Track permission event state
authorJan Kara <jack@suse.cz>
Tue, 8 Jan 2019 13:02:44 +0000 (14:02 +0100)
committerJan Kara <jack@suse.cz>
Mon, 18 Feb 2019 11:41:16 +0000 (12:41 +0100)
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>
fs/notify/fanotify/fanotify.c
fs/notify/fanotify/fanotify.h
fs/notify/fanotify/fanotify_user.c

index 4ff84bc5772e51f700c5f3a20c05d01767de1ec0..812c975df7eca4b3b11ba4dac08d9d0ed0753b3a 100644 (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);
index e84d68c6840a96654ae2da9864e0648f606caf87..480f281996d4cd015ad68725b8101ae925362554 100644 (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 */
 };
 
index a73ada49fd3e308ccc8301695431003e0ee0ce15..3c272f61d341822bfbb36f3be515176e2e0051dd 100644 (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);