drm/vmwgfx: validate external BO copy bounds for both stride paths

vmw_external_bo_copy() trusts caller-supplied offsets, strides, and
heights and operates on imported dma-buf vmaps:

  - The equal-stride memcpy() bound was clamped after subtracting the
    offsets from dst_size and src_size; an offset larger than the BO
    size wraps the unsigned subtraction to a huge value and the
    resulting memcpy() runs off the end of the vmap.  dst_stride *
    height is also a u32 multiplication that can overflow.
  - The non-equal-stride row-by-row path had no bound at all.  The
    loop touches bytes through offset + (height - 1) * stride +
    width_in_bytes, with only a WARN_ON(dst_stride < width_in_bytes),
    and could likewise step past the end of either mapping.

The offsets and strides are derived from STDU/SOU plane state, so a
configured CRTC submitting a crafted atomic commit on an imported
framebuffer can reach this path.

Validate the exact row-copy endpoint against each BO's size up front
using check_mul_overflow() and check_add_overflow().  Use the bulk
memcpy() path only when width_in_bytes covers the whole stride;
otherwise copy one row at a time so partial-row updates near the bottom
of a framebuffer remain valid.  Also reject zero strides and stride <
width_in_bytes, both of which the row-by-row path cannot represent
safely.

Fixes: 50f1199250 ("drm/vmwgfx: Fix prime with external buffers")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Zack Rusin <zack.rusin@broadcom.com>
Reviewed-by: Ian Forbes <ian.forbes@broadcom.com>
Link: https://patch.msgid.link/20260505222728.519626-13-zack.rusin@broadcom.com
This commit is contained in:
Zack Rusin
2026-07-27 11:29:24 -04:00
parent 54d56d5b42
commit 706c93c581
+31 -8
View File
@@ -30,6 +30,7 @@
#include "vmwgfx_bo.h"
#include <linux/highmem.h>
#include <linux/overflow.h>
/*
* Template that implements find_first_diff() for a generic
@@ -463,19 +464,42 @@ static int vmw_external_bo_copy(struct vmw_bo *dst, u32 dst_offset,
container_of(dst->tbo.bdev, struct vmw_private, bdev);
size_t dst_size = dst->tbo.resource->size;
size_t src_size = src->tbo.resource->size;
size_t dst_end, src_end;
struct iosys_map dst_map = {0};
struct iosys_map src_map = {0};
bool dst_mapped = false;
bool src_mapped = false;
int ret, i;
int x_in_bytes;
u8 *vsrc;
u8 *vdst;
if (!height || !width_in_bytes)
return 0;
if (!dst_stride || !src_stride)
return -EINVAL;
if (dst_stride < width_in_bytes || src_stride < width_in_bytes)
return -EINVAL;
if (check_mul_overflow((size_t)dst_stride, (size_t)height - 1, &dst_end) ||
check_add_overflow(dst_end, (size_t)width_in_bytes, &dst_end) ||
check_add_overflow((size_t)dst_offset, dst_end, &dst_end) ||
dst_end > dst_size ||
check_mul_overflow((size_t)src_stride, (size_t)height - 1, &src_end) ||
check_add_overflow(src_end, (size_t)width_in_bytes, &src_end) ||
check_add_overflow((size_t)src_offset, src_end, &src_end) ||
src_end > src_size) {
drm_dbg_driver(&vmw->drm, "Out-of-bounds external BO copy\n");
return -EINVAL;
}
vsrc = map_external(src, &src_map);
if (!vsrc) {
drm_dbg_driver(&vmw->drm, "Wasn't able to map src\n");
ret = -ENOMEM;
goto out;
}
src_mapped = true;
vdst = map_external(dst, &dst_map);
if (!vdst) {
@@ -483,16 +507,13 @@ static int vmw_external_bo_copy(struct vmw_bo *dst, u32 dst_offset,
ret = -ENOMEM;
goto out;
}
dst_mapped = true;
vsrc += src_offset;
vdst += dst_offset;
if (src_stride == dst_stride) {
dst_size -= dst_offset;
src_size -= src_offset;
memcpy(vdst, vsrc,
min(dst_stride * height, min(dst_size, src_size)));
if (src_stride == dst_stride && width_in_bytes == dst_stride) {
memcpy(vdst, vsrc, dst_stride * (size_t)height);
} else {
WARN_ON(dst_stride < width_in_bytes);
for (i = 0; i < height; ++i) {
memcpy(vdst, vsrc, width_in_bytes);
vsrc += src_stride;
@@ -508,8 +529,10 @@ static int vmw_external_bo_copy(struct vmw_bo *dst, u32 dst_offset,
ret = 0;
out:
unmap_external(src, &src_map);
unmap_external(dst, &dst_map);
if (src_mapped)
unmap_external(src, &src_map);
if (dst_mapped)
unmap_external(dst, &dst_map);
return ret;
}