msm: kgsl: Avoid race condition in ioctl_syncsource_destroy
If the ioctl syncsource_destroy is accessed by parallel
threads, where the spinlock is acquired by threads after
getting syncsource, then the simultaneous processes try
to remove the already destroyed syncsource->refcount by
the first thread that acquires this spinlock. This leads
to race condition while removing syncsource->idr.
Avoid separate lock inside getting syncsource, instead
acquire spinlock before we get the syncsource in
destroy ioctl so that the threads access the spinlock
and operate on syncsource without use-after-free issue.
Change-Id: I6add3800c40cd09f6e6e0cf2720e69059bd83cbc
Signed-off-by: Divya Ponnusamy <pdivya@codeaurora.org>
diff --git a/drivers/gpu/msm/kgsl_sync.c b/drivers/gpu/msm/kgsl_sync.c
index b80b699..eac5b5d 100644
--- a/drivers/gpu/msm/kgsl_sync.c
+++ b/drivers/gpu/msm/kgsl_sync.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -464,23 +464,23 @@
goto out;
}
+ kref_init(&syncsource->refcount);
+ syncsource->private = private;
+
idr_preload(GFP_KERNEL);
spin_lock(&private->syncsource_lock);
id = idr_alloc(&private->syncsource_idr, syncsource, 1, 0, GFP_NOWAIT);
- spin_unlock(&private->syncsource_lock);
- idr_preload_end();
-
if (id > 0) {
- kref_init(&syncsource->refcount);
syncsource->id = id;
- syncsource->private = private;
-
param->id = id;
ret = 0;
} else {
ret = id;
}
+ spin_unlock(&private->syncsource_lock);
+ idr_preload_end();
+
out:
if (ret) {
if (syncsource && syncsource->oneshot)
@@ -538,25 +538,23 @@
{
struct kgsl_syncsource_destroy *param = data;
struct kgsl_syncsource *syncsource = NULL;
- struct kgsl_process_private *private;
+ struct kgsl_process_private *private = dev_priv->process_priv;
- syncsource = kgsl_syncsource_get(dev_priv->process_priv,
- param->id);
+ spin_lock(&private->syncsource_lock);
+ syncsource = idr_find(&private->syncsource_idr, param->id);
+
+ if (syncsource) {
+ idr_remove(&private->syncsource_idr, param->id);
+ syncsource->id = 0;
+ }
+
+ spin_unlock(&private->syncsource_lock);
if (syncsource == NULL)
return -EINVAL;
- private = syncsource->private;
-
- spin_lock(&private->syncsource_lock);
- idr_remove(&private->syncsource_idr, param->id);
- syncsource->id = 0;
- spin_unlock(&private->syncsource_lock);
-
/* put reference from syncsource creation */
kgsl_syncsource_put(syncsource);
- /* put reference from getting the syncsource above */
- kgsl_syncsource_put(syncsource);
return 0;
}