Wednesday, December 8, 2010

[VULNERABILITY] CVE-2010-2962 in i915 graphics driver in Linux kernel

Vulnerable code ( found at drivers/gpu/drm/i915/i915_gem.c):

/**
 * Reads data from the object referenced by handle.
 *
 * On error, the contents of *data are undefined.
 */
    int
i915_gem_pread_ioctl(struct drm_device *dev, void *data,
        struct drm_file *file_priv)
{
    struct drm_i915_gem_pread *args = data;
    struct drm_gem_object *obj;
    struct drm_i915_gem_object *obj_priv;
    int ret;

    obj = drm_gem_object_lookup(dev, file_priv, args->handle);
    if (obj == NULL)
        return -ENOENT;
    obj_priv = to_intel_bo(obj);

    /* Bounds check source.
     *
     * XXX: This could use review for overflow issues...
     */
    if (args->offset > obj->size || args->size > obj->size ||
            args->offset + args->size > obj->size) {
        drm_gem_object_unreference_unlocked(obj);
        return -EINVAL;
    }

    if (i915_gem_object_needs_bit17_swizzle(obj)) {
        ret = i915_gem_shmem_pread_slow(dev, obj, args, file_priv);
    } else {
        ret = i915_gem_shmem_pread_fast(dev, obj, args, file_priv);
        if (ret != 0)
            ret = i915_gem_shmem_pread_slow(dev, obj, args,
                    file_priv);
    }

    drm_gem_object_unreference_unlocked(obj);

    return ret;
}

There is no check that user controlled data ‘args->data_ptr’ and ‘args->size’ are within the user-space’s limit. Because of this, a user could make ‘args->data_ptr’ pointing to some kernel memory and consequently forcing the above copy operations writing data to some arbitrary kernel memory instead of a userspace buffer.

Following patch fixes this situation:

@@ -477,8 +477,15 @@ i915_gem_pread_ioctl(struct drm_device *dev, void *data,
         */
        if (args->offset > obj->size || args->size > obj->size ||
            args->offset + args->size > obj->size) {
-               drm_gem_object_unreference_unlocked(obj);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
+       }
+
+       if (!access_ok(VERIFY_WRITE,
+                      (char __user *)(uintptr_t)args->data_ptr,
+                      args->size)) {
+               ret = -EFAULT;
+               goto err;
        }
        if (i915_gem_object_needs_bit17_swizzle(obj)) {
@@ -490,8 +497,8 @@ i915_gem_pread_ioctl(struct drm_device *dev, void *data,
                                                        file_priv);
        }
+err:
        drm_gem_object_unreference_unlocked(obj);
-
        return ret;
 }