drm/v3d: bound CPU-job query writes to their destination BO

The V3D_SUBMIT_CPU CPU jobs take user-supplied offsets and indices and
consume them at exec time without checking that the accesses stay inside
their BO:

  - TIMESTAMP_QUERY and RESET_TIMESTAMP_QUERY write one u64 per query
    into bo[0] at a fully user-controlled per-query offset.
  - COPY_TIMESTAMP_QUERY copies one u64 per query into bo[0] at
    offset + i * stride, and reads each result from a user-controlled
    offset in the source bo[1].
  - COPY_PERFORMANCE_QUERY writes nperfmons * DRM_V3D_MAX_PERF_COUNTERS
    counter slots plus an availability slot into bo[0] at the same
    geometry.
  - INDIRECT_CSD reads three u32 work-group counts from bo[0] at a
    user-controlled offset, then writes each count back into the
    indirect BO at a user-controlled u32 index (wg_uniform_offsets[]).

A render-node user (DRM_RENDER_ALLOW, no master, no capability) can make
the handlers read or write past a BO's vmap mapping.

Validate the full access extent against the BO size once the BOs are
looked up, before the job is queued, rejecting out-of-range geometry
with -EINVAL. The copy extent offset + (count - 1) * stride + write_size
is computed in u64, mirroring the u8 * pointer arithmetic in the
executors: (count - 1) * stride is a u32 * u32 product that is exact in
u64, so one overflow check on the total guards the bound. The
performance slot count and the bare timestamp, copy-source and indirect
offsets are computed in u64 the same way, so a user value cannot wrap
the comparison.

Fixes: 18b8413b25 ("drm/v3d: Create a CPU job extension for a indirect CSD job")
Fixes: 9ba0ff3e08 ("drm/v3d: Create a CPU job extension for the timestamp query job")
Fixes: 34a101e642 ("drm/v3d: Create a CPU job extension for the reset timestamp job")
Fixes: 6745f3e44a ("drm/v3d: Create a CPU job extension to copy timestamp query to a buffer")
Fixes: 209e8d2695 ("drm/v3d: Create a CPU job extension for the copy performance query job")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Reviewed-by: Maíra Canal <mcanal@igalia.com>
Signed-off-by: Maíra Canal <mcanal@igalia.com>
Link: https://patch.msgid.link/20260707221334.3854433-1-michael.bommarito@gmail.com
This commit is contained in:
Michael Bommarito 2026-07-07 18:13:34 -04:00 committed by Maíra Canal
parent b5a8b1f2a9
commit 671b7825db
No known key found for this signature in database
GPG Key ID: 3FF30E8A7688FAAA

View File

@ -5,6 +5,7 @@
*/
#include <linux/dma-fence-unwrap.h>
#include <linux/overflow.h>
#include <drm/drm_print.h>
#include <drm/drm_syncobj.h>
@ -1377,6 +1378,126 @@ static const unsigned int cpu_job_bo_handle_count[] = {
[V3D_CPU_JOB_TYPE_COPY_PERFORMANCE_QUERY] = 1,
};
/* Reject offset + (count - 1) * stride + write_size if it leaves the BO. */
static int
v3d_check_copy_extent(struct drm_device *dev, size_t bo_size,
u32 offset, u32 stride, u32 count, u64 write_size)
{
u64 last;
if (!count)
return 0;
/*
* The executors walk a u8 * cursor, so the furthest written byte is
* offset + (count - 1) * stride + write_size, matching the pointer
* arithmetic in v3d_copy_query_results()/v3d_copy_performance_query().
* (count - 1) * stride is a u32 * u32 product that is exact in u64,
* and offset + write_size stays far below the u64 range, so a single
* overflow check guards the total.
*/
last = write_size + offset;
if (check_add_overflow((u64)(count - 1) * stride, last, &last) ||
last > bo_size) {
drm_dbg(dev, "CPU job copy buffer exceeds the destination BO.\n");
return -EINVAL;
}
return 0;
}
/* Reject a query CPU job whose writes would land outside their BO. */
static int
v3d_cpu_job_bounds_check(struct v3d_cpu_job *job)
{
struct drm_device *dev = &job->base.v3d->drm;
struct v3d_timestamp_query_info *tquery = &job->timestamp_query;
struct v3d_copy_query_results_info *copy = &job->copy;
u32 elem = copy->do_64bit ? sizeof(u64) : sizeof(u32);
struct v3d_bo *dst, *src;
u64 slots, write_size;
u32 i;
switch (job->job_type) {
case V3D_CPU_JOB_TYPE_TIMESTAMP_QUERY:
case V3D_CPU_JOB_TYPE_RESET_TIMESTAMP_QUERY:
/* Each query writes one u64 timestamp slot into bo[0]. */
dst = to_v3d_bo(job->base.bo[0]);
for (i = 0; i < tquery->count; i++) {
if ((u64)tquery->queries[i].offset + sizeof(u64) >
dst->base.base.size)
goto err_range;
}
return 0;
case V3D_CPU_JOB_TYPE_COPY_TIMESTAMP_QUERY:
/* Copies one u64 per query from bo[1] into bo[0]. */
dst = to_v3d_bo(job->base.bo[0]);
src = to_v3d_bo(job->base.bo[1]);
for (i = 0; i < tquery->count; i++) {
if ((u64)tquery->queries[i].offset + sizeof(u64) >
src->base.base.size)
goto err_range;
}
write_size = (copy->availability_bit ? 2 : 1) * elem;
return v3d_check_copy_extent(dev, dst->base.base.size,
copy->offset, copy->stride,
tquery->count, write_size);
case V3D_CPU_JOB_TYPE_COPY_PERFORMANCE_QUERY:
/*
* Each query writes nperfmons * DRM_V3D_MAX_PERF_COUNTERS
* counter slots into bo[0], plus an availability slot at
* index ncounters. nperfmons and ncounters are user values,
* so the slot count is computed overflow-safe.
*/
dst = to_v3d_bo(job->base.bo[0]);
slots = (u64)job->performance_query.nperfmons *
DRM_V3D_MAX_PERF_COUNTERS;
if (copy->availability_bit)
slots = max(slots,
(u64)job->performance_query.ncounters + 1);
write_size = slots * elem;
return v3d_check_copy_extent(dev, dst->base.base.size,
copy->offset, copy->stride,
job->performance_query.count,
write_size);
case V3D_CPU_JOB_TYPE_INDIRECT_CSD: {
struct v3d_indirect_csd_info *indirect_csd = &job->indirect_csd;
/* 3 is the three dimensions (x, y, z) of the workgroup counts. */
src = to_v3d_bo(job->base.bo[0]);
if ((u64)indirect_csd->offset + 3 * sizeof(u32) >
src->base.base.size)
goto err_range;
dst = to_v3d_bo(indirect_csd->indirect);
for (i = 0; i < 3; i++) {
u32 uidx = indirect_csd->wg_uniform_offsets[i];
/*
* 0xffffffff means "skip this rewrite", so the exec
* path never writes that index and it needs no check.
*/
if (uidx != 0xffffffff &&
(u64)uidx * sizeof(u32) + sizeof(u32) >
dst->base.base.size)
goto err_range;
}
return 0;
}
default:
return 0;
}
err_range:
drm_dbg(dev, "CPU job query offset exceeds the BO.\n");
return -EINVAL;
}
/**
* v3d_submit_cpu_ioctl() - Submits a CPU job to the V3D.
* @dev: DRM device
@ -1439,6 +1560,10 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
ret = v3d_lookup_bos(&submit, args->bo_handles, args->bo_handle_count);
if (ret)
goto fail;
ret = v3d_cpu_job_bounds_check(cpu_job);
if (ret)
goto fail;
}
if (cpu_job->job_type == V3D_CPU_JOB_TYPE_INDIRECT_CSD) {