drm/vmwgfx: Reinstate and tighten security around legacy master model
authorThomas Hellstrom <thellstrom@vmware.com>
Thu, 27 Feb 2014 11:56:08 +0000 (12:56 +0100)
committerThomas Hellstrom <thellstrom@vmware.com>
Fri, 28 Mar 2014 13:19:03 +0000 (14:19 +0100)
The following restrictions affect clients connecting using legacy nodes:

*) Masters that have dropped master privilieges are not considered
   authenticated until they regain master privileges.
*) Clients whose master have dropped master privileges block interruptibly on
   ioctls  requiring authentication until their master regains master
   privileges. If their master exits, they are killed.

This is primarily designed to prevent clients authenticated with one master to
access data from clients authenticated with another master.
(Think fast user-switching or data sniffers enabled while X is vt-switched).

Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: Brian Paul <brianp@vmware.com>
drivers/gpu/drm/vmwgfx/vmwgfx_drv.c

index 54cfeb6fc3b160a3885ea933af3bc3f0a22ca37b..8fdbe26a98d2c645f21665ac5c9aa565585a55ab 100644 (file)
@@ -982,12 +982,70 @@ out_no_tfile:
        return ret;
 }
 
-static long vmw_unlocked_ioctl(struct file *filp, unsigned int cmd,
-                              unsigned long arg)
+static struct vmw_master *vmw_master_check(struct drm_device *dev,
+                                          struct drm_file *file_priv,
+                                          unsigned int flags)
+{
+       int ret;
+       struct vmw_fpriv *vmw_fp = vmw_fpriv(file_priv);
+       struct vmw_master *vmaster;
+
+       if (file_priv->minor->type != DRM_MINOR_LEGACY ||
+           !(flags & DRM_AUTH))
+               return NULL;
+
+       ret = mutex_lock_interruptible(&dev->master_mutex);
+       if (unlikely(ret != 0))
+               return ERR_PTR(-ERESTARTSYS);
+
+       if (file_priv->is_master) {
+               mutex_unlock(&dev->master_mutex);
+               return NULL;
+       }
+
+       /*
+        * Check if we were previously master, but now dropped.
+        */
+       if (vmw_fp->locked_master) {
+               mutex_unlock(&dev->master_mutex);
+               DRM_ERROR("Dropped master trying to access ioctl that "
+                         "requires authentication.\n");
+               return ERR_PTR(-EACCES);
+       }
+       mutex_unlock(&dev->master_mutex);
+
+       /*
+        * Taking the drm_global_mutex after the TTM lock might deadlock
+        */
+       if (!(flags & DRM_UNLOCKED)) {
+               DRM_ERROR("Refusing locked ioctl access.\n");
+               return ERR_PTR(-EDEADLK);
+       }
+
+       /*
+        * Take the TTM lock. Possibly sleep waiting for the authenticating
+        * master to become master again, or for a SIGTERM if the
+        * authenticating master exits.
+        */
+       vmaster = vmw_master(file_priv->master);
+       ret = ttm_read_lock(&vmaster->lock, true);
+       if (unlikely(ret != 0))
+               vmaster = ERR_PTR(ret);
+
+       return vmaster;
+}
+
+static long vmw_generic_ioctl(struct file *filp, unsigned int cmd,
+                             unsigned long arg,
+                             long (*ioctl_func)(struct file *, unsigned int,
+                                                unsigned long))
 {
        struct drm_file *file_priv = filp->private_data;
        struct drm_device *dev = file_priv->minor->dev;
        unsigned int nr = DRM_IOCTL_NR(cmd);
+       struct vmw_master *vmaster;
+       unsigned int flags;
+       long ret;
 
        /*
         * Do extra checking on driver private ioctls.
@@ -996,18 +1054,44 @@ static long vmw_unlocked_ioctl(struct file *filp, unsigned int cmd,
        if ((nr >= DRM_COMMAND_BASE) && (nr < DRM_COMMAND_END)
            && (nr < DRM_COMMAND_BASE + dev->driver->num_ioctls)) {
                const struct drm_ioctl_desc *ioctl =
-                   &vmw_ioctls[nr - DRM_COMMAND_BASE];
+                       &vmw_ioctls[nr - DRM_COMMAND_BASE];
 
                if (unlikely(ioctl->cmd_drv != cmd)) {
                        DRM_ERROR("Invalid command format, ioctl %d\n",
                                  nr - DRM_COMMAND_BASE);
                        return -EINVAL;
                }
+               flags = ioctl->flags;
+       } else if (!drm_ioctl_flags(nr, &flags))
+               return -EINVAL;
+
+       vmaster = vmw_master_check(dev, file_priv, flags);
+       if (unlikely(IS_ERR(vmaster))) {
+               DRM_INFO("IOCTL ERROR %d\n", nr);
+               return PTR_ERR(vmaster);
        }
 
-       return drm_ioctl(filp, cmd, arg);
+       ret = ioctl_func(filp, cmd, arg);
+       if (vmaster)
+               ttm_read_unlock(&vmaster->lock);
+
+       return ret;
+}
+
+static long vmw_unlocked_ioctl(struct file *filp, unsigned int cmd,
+                              unsigned long arg)
+{
+       return vmw_generic_ioctl(filp, cmd, arg, &drm_ioctl);
 }
 
+#ifdef CONFIG_COMPAT
+static long vmw_compat_ioctl(struct file *filp, unsigned int cmd,
+                            unsigned long arg)
+{
+       return vmw_generic_ioctl(filp, cmd, arg, &drm_compat_ioctl);
+}
+#endif
+
 static void vmw_lastclose(struct drm_device *dev)
 {
        struct drm_crtc *crtc;
@@ -1315,7 +1399,7 @@ static const struct file_operations vmwgfx_driver_fops = {
        .poll = vmw_fops_poll,
        .read = vmw_fops_read,
 #if defined(CONFIG_COMPAT)
-       .compat_ioctl = drm_compat_ioctl,
+       .compat_ioctl = vmw_compat_ioctl,
 #endif
        .llseek = noop_llseek,
 };