In
commit cdd1cf799bd24ac0a4184549601ae302267301c5 Author: Chris Wilson chris@chris-wilson.co.uk Date: Thu Dec 4 21:03:25 2014 +0000
drm: Make drm_read() more robust against multithreaded races
I fixed the races by serialising the use of the event by extending the dev->event_lock. However, as Thomas pointed out, the copy_to_user() may fault (even the __copy_to_user_inatomic() variant used here) and calling into the driver backend with the spinlock held is bad news. Therefore we have to drop the spinlock before the copy, but that exposes us to the old race whereby a second reader could see an out-of-order event (as the first reader may claim the first request but fail to copy it back to userspace and so on returning it to the event list it will be behind the current event being copied by the second reader).
Reported-by: Thomas Hellstrom thellstrom@vmware.com Signed-off-by: Chris Wilson chris@chris-wilson.co.uk Cc: Thomas Hellstrom thellstrom@vmware.com Cc: Takashi Iwai tiwai@suse.de Cc: Daniel Vetter daniel.vetter@ffwll.ch --- drivers/gpu/drm/drm_fops.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index c59ce4d0ef75..eb8702d39e7d 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -488,9 +488,19 @@ ssize_t drm_read(struct file *filp, char __user *buffer, if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT;
- spin_lock_irq(&dev->event_lock); for (;;) { - if (list_empty(&file_priv->event_list)) { + struct drm_pending_event *e = NULL; + + spin_lock_irq(&dev->event_lock); + if (!list_empty(&file_priv->event_list)) { + e = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + file_priv->event_space += e->event->length; + list_del(&e->link); + } + spin_unlock_irq(&dev->event_lock); + + if (e == NULL) { if (ret) break;
@@ -499,36 +509,34 @@ ssize_t drm_read(struct file *filp, char __user *buffer, break; }
- spin_unlock_irq(&dev->event_lock); ret = wait_event_interruptible(file_priv->event_wait, !list_empty(&file_priv->event_list)); - spin_lock_irq(&dev->event_lock); if (ret < 0) break;
ret = 0; } else { - struct drm_pending_event *e; - - e = list_first_entry(&file_priv->event_list, - struct drm_pending_event, link); - if (e->event->length + ret > count) + unsigned length = e->event->length; + + if (length > count - ret) { +put_back_event: + spin_lock_irq(&dev->event_lock); + file_priv->event_space -= length; + list_add(&e->link, &file_priv->event_list); + spin_unlock_irq(&dev->event_lock); break; + }
- if (__copy_to_user_inatomic(buffer + ret, - e->event, e->event->length)) { + if (copy_to_user(buffer + ret, e->event, length)) { if (ret == 0) ret = -EFAULT; - break; + goto put_back_event; }
- file_priv->event_space += e->event->length; - ret += e->event->length; - list_del(&e->link); + ret += length; e->destroy(e); } } - spin_unlock_irq(&dev->event_lock);
return ret; }
The previous patch reintroduced a race condition whereby a failure in one reader may allow a second reader to see out-of-order events. Introduce a mutex to serialise readers so that an event is completed in its entirety before another reader may process an event. The two readers may race against each other, but the events each retrieves are in the correct order.
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk Cc: Thomas Hellstrom thellstrom@vmware.com Cc: Takashi Iwai tiwai@suse.de Cc: Daniel Vetter daniel.vetter@ffwll.ch --- drivers/gpu/drm/drm_fops.c | 18 +++++++++++++----- include/drm/drmP.h | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index eb8702d39e7d..81df9ae95e2e 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -172,6 +172,8 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor) init_waitqueue_head(&priv->event_wait); priv->event_space = 4096; /* set aside 4k for event buffer */
+ mutex_init(&priv->event_read_lock); + if (drm_core_check_feature(dev, DRIVER_GEM)) drm_gem_open(dev, priv);
@@ -483,11 +485,15 @@ ssize_t drm_read(struct file *filp, char __user *buffer, { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; - ssize_t ret = 0; + ssize_t ret;
if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT;
+ ret = mutex_lock_interruptible(&file_priv->event_read_lock); + if (ret) + return ret; + for (;;) { struct drm_pending_event *e = NULL;
@@ -509,12 +515,13 @@ ssize_t drm_read(struct file *filp, char __user *buffer, break; }
+ mutex_unlock(&file_priv->event_read_lock); ret = wait_event_interruptible(file_priv->event_wait, !list_empty(&file_priv->event_list)); - if (ret < 0) - break; - - ret = 0; + if (ret >= 0) + ret = mutex_lock_interruptible(&file_priv->event_read_lock); + if (ret) + return ret; } else { unsigned length = e->event->length;
@@ -537,6 +544,7 @@ put_back_event: e->destroy(e); } } + mutex_unlock(&file_priv->event_read_lock);
return ret; } diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 30d4a5a495e2..8e1df1f7057c 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -344,6 +344,8 @@ struct drm_file { struct list_head event_list; int event_space;
+ struct mutex event_read_lock; + struct drm_prime_file_private prime; };
Do you need to take the mutex around other event pullers as well? So that no such process thinks it has pulled all events and then suddenly an event reappears?
I think there was some event pulling code in one of the drivers, but I might be wrong. The close() code should be safe against this.
/Thomas
On 11/25/2015 03:39 PM, Chris Wilson wrote:
The previous patch reintroduced a race condition whereby a failure in one reader may allow a second reader to see out-of-order events. Introduce a mutex to serialise readers so that an event is completed in its entirety before another reader may process an event. The two readers may race against each other, but the events each retrieves are in the correct order.
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk Cc: Thomas Hellstrom thellstrom@vmware.com Cc: Takashi Iwai tiwai@suse.de Cc: Daniel Vetter daniel.vetter@ffwll.ch
drivers/gpu/drm/drm_fops.c | 18 +++++++++++++----- include/drm/drmP.h | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index eb8702d39e7d..81df9ae95e2e 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -172,6 +172,8 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor) init_waitqueue_head(&priv->event_wait); priv->event_space = 4096; /* set aside 4k for event buffer */
- mutex_init(&priv->event_read_lock);
- if (drm_core_check_feature(dev, DRIVER_GEM)) drm_gem_open(dev, priv);
@@ -483,11 +485,15 @@ ssize_t drm_read(struct file *filp, char __user *buffer, { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev;
- ssize_t ret = 0;
ssize_t ret;
if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT;
ret = mutex_lock_interruptible(&file_priv->event_read_lock);
if (ret)
return ret;
for (;;) { struct drm_pending_event *e = NULL;
@@ -509,12 +515,13 @@ ssize_t drm_read(struct file *filp, char __user *buffer, break; }
mutex_unlock(&file_priv->event_read_lock); ret = wait_event_interruptible(file_priv->event_wait, !list_empty(&file_priv->event_list));
if (ret < 0)
break;
ret = 0;
if (ret >= 0)
ret = mutex_lock_interruptible(&file_priv->event_read_lock);
if (ret)
} else { unsigned length = e->event->length;return ret;
@@ -537,6 +544,7 @@ put_back_event: e->destroy(e); } }
mutex_unlock(&file_priv->event_read_lock);
return ret;
} diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 30d4a5a495e2..8e1df1f7057c 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -344,6 +344,8 @@ struct drm_file { struct list_head event_list; int event_space;
- struct mutex event_read_lock;
- struct drm_prime_file_private prime;
};
On Wed, Nov 25, 2015 at 03:44:04PM +0100, Thomas Hellstrom wrote:
Do you need to take the mutex around other event pullers as well?
We would. I checked in drm/*.c for other users, but not the drivers. A quick git grep doesn't show any likely candidates, they appear to be private event lists.
So that no such process thinks it has pulled all events and then suddenly an event reappears?
A short read just implies that the kernel returned all the events it has. That doesn't imply any new ones haven't manifested in the time it takes you to see the new events. (You either call read again until it EAGAINs, or go back to poll.)
I think there was some event pulling code in one of the drivers, but I might be wrong.
I hope not...
The close() code should be safe against this.
I checked through drm_release and decided that since it cannot happen whilst drm_read() is active and so I didn't need to worry about having to break the lock or stop the read.
Anything else of concern? -Chris
On 11/25/2015 03:56 PM, Chris Wilson wrote:
On Wed, Nov 25, 2015 at 03:44:04PM +0100, Thomas Hellstrom wrote:
Do you need to take the mutex around other event pullers as well?
We would. I checked in drm/*.c for other users, but not the drivers. A quick git grep doesn't show any likely candidates, they appear to be private event lists.
Indeed. I was confused by some exynos code I didn't look at too carefully. Also vmwgfx has some code to pull events, but it is not called until release time so it can't race.
So for the series: Reviewed-by: Thomas Hellstrom thellstrom@vmware.com
Thanks for fixing this.
/Thomas
On Thu, Nov 26, 2015 at 12:19:43PM +0100, Thomas Hellstrom wrote:
On 11/25/2015 03:56 PM, Chris Wilson wrote:
On Wed, Nov 25, 2015 at 03:44:04PM +0100, Thomas Hellstrom wrote:
Do you need to take the mutex around other event pullers as well?
We would. I checked in drm/*.c for other users, but not the drivers. A quick git grep doesn't show any likely candidates, they appear to be private event lists.
Indeed. I was confused by some exynos code I didn't look at too carefully. Also vmwgfx has some code to pull events, but it is not called until release time so it can't race.
So for the series: Reviewed-by: Thomas Hellstrom thellstrom@vmware.com
Thanks for patches&review, applied to drm-misc. -Daniel
dri-devel@lists.freedesktop.org