The QXL execbuffer ioctl lets userspace request relocations into a
destination buffer object at an arbitrary offset.  The offset is never
validated against the size of that object, and the relocation is applied
with a plain pointer write into the object's io-mapping window:

  apply_reloc():
      reloc_page = qxl_bo_kmap_atomic_page(qdev, dst_bo, dst_offset & 
PAGE_MASK);
      *(uint64_t *)(reloc_page + (dst_offset & ~PAGE_MASK)) = ...;

  apply_surf_reloc():
      reloc_page = qxl_bo_kmap_atomic_page(qdev, dst_bo, dst_offset & 
PAGE_MASK);
      *(uint32_t *)(reloc_page + (dst_offset & ~PAGE_MASK)) = id;

so a user supplied dst_offset turns into an out-of-bounds kernel write of
8 (or 4) bytes.  As the offset is additionally truncated into a 32bit
member and handed on as "int page_offset" to qxl_bo_kmap_atomic_page(),
the write can even be placed before the object (sign extension).

Validate the offset plus the access size against the destination object
size, require the offset to be aligned to the access size so the write
can never straddle a page boundary, and make the offset/parameter types
wide enough that the check is not defeated by truncation or sign
extension.

Fixes: f64122c1f6ad ("drm: add new QXL driver. (v1.4)")
Cc: [email protected]
Signed-off-by: Zhou Qiuyue <[email protected]>
---
v3:
 - require dst_offset to be aligned to reloc_size so the write can never
   straddle a page boundary inside the kmap window (addresses Sashiko's
   alignment finding).

v2:
 - use a real-name Signed-off-by (noted by Willy Tarreau).
 - the reproducer and full report were sent to the maintainers on 2026-10-06;
   the reproducer can be shared on request.
 drivers/gpu/drm/qxl/qxl_ioctl.c  | 23 ++++++++++++++++++++++-
 drivers/gpu/drm/qxl/qxl_object.c |  2 +-
 drivers/gpu/drm/qxl/qxl_object.h |  2 +-
 3 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/qxl/qxl_ioctl.c b/drivers/gpu/drm/qxl/qxl_ioctl.c
index 591b026..9a031bb 100755
--- a/drivers/gpu/drm/qxl/qxl_ioctl.c
+++ b/drivers/gpu/drm/qxl/qxl_ioctl.c
@@ -73,7 +73,7 @@ int qxl_map_ioctl(struct drm_device *dev, void *data, struct 
drm_file *file_priv
 struct qxl_reloc_info {
        int type;
        struct qxl_bo *dst_bo;
-       uint32_t dst_offset;
+       uint64_t dst_offset;
        struct qxl_bo *src_bo;
        int src_offset;
 };
@@ -149,6 +149,7 @@ static int qxl_process_single_command(struct qxl_device 
*qdev,
        void *fb_cmd;
        int i, ret;
        int unwritten;
+       u32 reloc_size;
 
        switch (cmd->type) {
        case QXL_CMD_DRAW:
@@ -231,6 +232,26 @@ static int qxl_process_single_command(struct qxl_device 
*qdev,
                        reloc_info[i].dst_offset = reloc.dst_offset + 
release->release_offset;
                }
 
+               /*
+                * The relocation is applied with a plain write into the memory
+                * window of the destination object, so the user supplied offset
+                * must be validated against the object size.  Otherwise 
userspace
+                * can write anywhere in the mapping window set up by
+                * qxl_bo_kmap_atomic_page(), i.e. outside of the object.  The
+                * offset must also be aligned to the access size so the write
+                * cannot straddle a page boundary inside the window.
+                */
+               reloc_size = (reloc.reloc_type == QXL_RELOC_TYPE_BO) ?
+                            sizeof(uint64_t) : sizeof(uint32_t);
+               if (!IS_ALIGNED(reloc_info[i].dst_offset, reloc_size) ||
+                   reloc_info[i].dst_offset > 
reloc_info[i].dst_bo->tbo.base.size ||
+                   reloc_size > reloc_info[i].dst_bo->tbo.base.size -
+                                reloc_info[i].dst_offset) {
+                       DRM_DEBUG("reloc dst offset out of bounds\n");
+                       ret = -EINVAL;
+                       goto out_free_bos;
+               }
+
                /* reserve and validate the reloc dst bo */
                if (reloc.reloc_type == QXL_RELOC_TYPE_BO || reloc.src_handle) {
                        ret = qxlhw_handle_to_bo(file_priv, reloc.src_handle, 
release,
diff --git a/drivers/gpu/drm/qxl/qxl_object.c b/drivers/gpu/drm/qxl/qxl_object.c
index 313f6c3..0df4814 100755
--- a/drivers/gpu/drm/qxl/qxl_object.c
+++ b/drivers/gpu/drm/qxl/qxl_object.c
@@ -204,7 +204,7 @@ int qxl_bo_pin_and_vmap(struct qxl_bo *bo, struct iosys_map 
*map)
 }
 
 void *qxl_bo_kmap_atomic_page(struct qxl_device *qdev,
-                             struct qxl_bo *bo, int page_offset)
+                             struct qxl_bo *bo, unsigned long page_offset)
 {
        unsigned long offset;
        void *rptr;
diff --git a/drivers/gpu/drm/qxl/qxl_object.h b/drivers/gpu/drm/qxl/qxl_object.h
index 875f632..a4aed9d 100755
--- a/drivers/gpu/drm/qxl/qxl_object.h
+++ b/drivers/gpu/drm/qxl/qxl_object.h
@@ -63,7 +63,7 @@ int qxl_bo_pin_and_vmap(struct qxl_bo *bo, struct iosys_map 
*map);
 int qxl_bo_vmap_locked(struct qxl_bo *bo, struct iosys_map *map);
 int qxl_bo_vunmap_and_unpin(struct qxl_bo *bo);
 void qxl_bo_vunmap_locked(struct qxl_bo *bo);
-void *qxl_bo_kmap_atomic_page(struct qxl_device *qdev, struct qxl_bo *bo, int 
page_offset);
+void *qxl_bo_kmap_atomic_page(struct qxl_device *qdev, struct qxl_bo *bo, 
unsigned long page_offset);
 void qxl_bo_kunmap_atomic_page(struct qxl_device *qdev, struct qxl_bo *bo, 
void *map);
 extern struct qxl_bo *qxl_bo_ref(struct qxl_bo *bo);
 extern void qxl_bo_unref(struct qxl_bo **bo);

Reply via email to