gpu: nvgpu: fix use-after-free in case of error notifier
Gagan Grover [Wed, 12 Oct 2016 11:35:06 +0000 (16:35 +0530)]
A use-after-free scenario is possible where one thread in
gk20a_free_error_notifiers() is trying to free the error
notifier and another thread in gk20a_set_error_notifier()
is still using the error notifier

Fix this by introducing mutex error_notifier_mutex for
error notifier accesses

Take mutex in gk20a_free_error_notifiers() and in
gk20a_set_error_notifier() before accessing notifier

In gk20a_init_error_notifier(), set the pointer
ch->error_notifier_ref inside the mutex and only
after notifier is completely initialized

Bug 1824788

Change-Id: I47e1ab57d54f391799f5a0999840b663fd34585f
Reviewed-on: http://git-master/r/1233988
Signed-off-by: Gagan Grover <ggrover@nvidia.com>
Signed-off-by: Gaurav Singh <gaursingh@nvidia.com>
Reviewed-on: http://git-master/r/1236695
GVS: Gerrit_Virtual_Submit
Reviewed-by: Deepak Nibade <dnibade@nvidia.com>
Reviewed-by: Bibek Basu <bbasu@nvidia.com>

drivers/gpu/nvgpu/gk20a/channel_gk20a.c
drivers/gpu/nvgpu/gk20a/channel_gk20a.h

index f64bda9..b1b59a9 100644 (file)
@@ -552,8 +552,7 @@ static int gk20a_init_error_notifier(struct channel_gk20a *ch,
 
        dmabuf = dma_buf_get(args->mem);
 
-       if (ch->error_notifier_ref)
-               gk20a_free_error_notifiers(ch);
+       gk20a_free_error_notifiers(ch);
 
        if (IS_ERR(dmabuf)) {
                pr_err("Invalid handle: %d\n", args->mem);
@@ -574,16 +573,23 @@ static int gk20a_init_error_notifier(struct channel_gk20a *ch,
                return -ENOMEM;
        }
 
-       /* set channel notifiers pointer */
-       ch->error_notifier_ref = dmabuf;
        ch->error_notifier = va + args->offset;
        ch->error_notifier_va = va;
        memset(ch->error_notifier, 0, sizeof(struct nvhost_notification));
+
+       /* set channel notifiers pointer */
+       mutex_lock(&ch->error_notifier_mutex);
+       ch->error_notifier_ref = dmabuf;
+       mutex_unlock(&ch->error_notifier_mutex);
+
        return 0;
 }
 
 void gk20a_set_error_notifier(struct channel_gk20a *ch, __u32 error)
 {
+       bool notifier_set = false;
+
+       mutex_lock(&ch->error_notifier_mutex);
        if (ch->error_notifier_ref) {
                struct timespec time_data;
                u64 nsec;
@@ -596,20 +602,27 @@ void gk20a_set_error_notifier(struct channel_gk20a *ch, __u32 error)
                                (u32)(nsec >> 32);
                ch->error_notifier->info32 = error;
                ch->error_notifier->status = 0xffff;
-               gk20a_err(dev_from_gk20a(ch->g),
-                               "error notifier set to %d\n", error);
+
+               notifier_set = true;
        }
+       mutex_unlock(&ch->error_notifier_mutex);
+
+       if (notifier_set)
+               gk20a_err(dev_from_gk20a(ch->g),
+                   "error notifier set to %d for ch %d", error, ch->hw_chid);
 }
 
 static void gk20a_free_error_notifiers(struct channel_gk20a *ch)
 {
+       mutex_lock(&ch->error_notifier_mutex);
        if (ch->error_notifier_ref) {
                dma_buf_vunmap(ch->error_notifier_ref, ch->error_notifier_va);
                dma_buf_put(ch->error_notifier_ref);
-               ch->error_notifier_ref = 0;
-               ch->error_notifier = 0;
-               ch->error_notifier_va = 0;
+               ch->error_notifier_ref = NULL;
+               ch->error_notifier = NULL;
+               ch->error_notifier_va = NULL;
        }
+       mutex_unlock(&ch->error_notifier_mutex);
 }
 
 void gk20a_free_channel(struct channel_gk20a *ch, bool finish)
@@ -1630,6 +1643,7 @@ int gk20a_init_channel_support(struct gk20a *g, u32 chid)
        c->bound = false;
        c->remove_support = gk20a_remove_channel_support;
        mutex_init(&c->jobs_lock);
+       mutex_init(&c->error_notifier_mutex);
        INIT_LIST_HEAD(&c->jobs);
 #if defined(CONFIG_GK20A_CYCLE_STATS)
        mutex_init(&c->cyclestate.cyclestate_buffer_mutex);
index 320ada6..547bb06 100644 (file)
@@ -132,6 +132,7 @@ struct channel_gk20a {
        struct dma_buf *error_notifier_ref;
        struct nvhost_notification *error_notifier;
        void *error_notifier_va;
+       struct mutex error_notifier_mutex;
 
        struct gk20a_channel_sync *sync;
 };