qxl_process_single_command() copies relocation entries from userspace and
passes them to apply_reloc() / apply_surf_reloc() without checking
whether dst_offset falls within the destination buffer object.

apply_reloc() writes 8 bytes and apply_surf_reloc() writes 4 bytes at
the byte offset named by the relocation.  A userspace-chosen offset that
exceeds the BO's allocation leads to an out-of-bounds write into
adjacent slab memory.

When the destination is the command/release BO itself (dst_handle == 0),
the write lands relative to the release's own release_info header.  An
offset of zero overwrites the release_info.id, which the garbage
collector later uses as a release index, leading to a use-after-free of
a release chosen by userspace.  Both paths are reachable from any render
client (DRM_AUTH).

Add two bounds checks:

  - When writing into the release's own BO (dst_handle == 0), require
    that dst_offset points past the release_info header and stays within
    the command data area.

  - For every relocation, require that the final byte offset plus the
    write width does not exceed the destination BO size.

Fixes: f64122c1f6ad ("drm: add qxl driver.")
Cc: [email protected]
Signed-off-by: Aldo Ariel Panzardo <[email protected]>
---
 drivers/gpu/drm/qxl/qxl_ioctl.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/gpu/drm/qxl/qxl_ioctl.c b/drivers/gpu/drm/qxl/qxl_ioctl.c
index 591b026ce..b62f32b6d 100644
--- a/drivers/gpu/drm/qxl/qxl_ioctl.c
+++ b/drivers/gpu/drm/qxl/qxl_ioctl.c
@@ -227,10 +227,27 @@ static int qxl_process_single_command(struct qxl_device 
*qdev,
                                goto out_free_bos;
                        reloc_info[i].dst_offset = reloc.dst_offset;
                } else {
+                       if (reloc.dst_offset < sizeof(union qxl_release_info) ||
+                           reloc.dst_offset >= sizeof(union qxl_release_info) +
+                                               cmd->command_size) {
+                               ret = -EINVAL;
+                               goto out_free_bos;
+                       }
                        reloc_info[i].dst_bo = cmd_bo;
                        reloc_info[i].dst_offset = reloc.dst_offset + 
release->release_offset;
                }
 
+               {
+                       size_t write_size = reloc.reloc_type == 
QXL_RELOC_TYPE_BO ?
+                                           sizeof(uint64_t) : sizeof(uint32_t);
+
+                       if (reloc_info[i].dst_offset + write_size >
+                           reloc_info[i].dst_bo->tbo.base.size) {
+                               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,
-- 
2.43.0

Reply via email to