From 112badb7245059c176e3924f9cd9ce2933cb0082 Mon Sep 17 00:00:00 2001 From: Alessio Belle Date: Mon, 20 Jul 2026 16:07:28 +0100 Subject: [PATCH 01/21] drm/imagination: Fix value of HWRT address in pvr_job_create tracepoint The pvr_job_create tracepoint was using pvr_fw_object::fw_addr_offset to display the firmware address of the render target structure attached to a job, but that's an offset into the firmware heap, not the expected full address, which is more useful in general e.g. for cross referencing against firmware logs. Use pvr_fw_object_get_fw_addr() to get the full firmware address. Fixes: c1079aebb4de ("drm/imagination: Add support for trace points") Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20260720-fix-pvr-job-trace-hwrt-v1-1-b24551802efb@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_trace.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_trace.h b/drivers/gpu/drm/imagination/pvr_trace.h index ffbbde3b2bcc..943d6b2418cb 100644 --- a/drivers/gpu/drm/imagination/pvr_trace.h +++ b/drivers/gpu/drm/imagination/pvr_trace.h @@ -41,6 +41,17 @@ TRACE_EVENT(pvr_job_submit_ioctl, __entry->count) ); +#define PVR_JOB_GET_HWRT_FW_ADDR(job) \ + ({ \ + struct pvr_job *_job = (job); \ + u32 _hwrt_fw_addr = 0; \ + \ + if (_job && _job->hwrt) \ + pvr_fw_object_get_fw_addr(_job->hwrt->fw_obj, &_hwrt_fw_addr); \ + \ + _hwrt_fw_addr; \ + }) + #define PVR_JOB_TYPE_TO_STR(val) \ __print_symbolic(val, \ { DRM_PVR_JOB_TYPE_GEOMETRY, "geometry" }, \ @@ -64,9 +75,7 @@ TRACE_EVENT(pvr_job_create, __entry->ctx = job->ctx; __entry->fw_obj = job->ctx->fw_obj; pvr_fw_object_get_fw_addr(job->ctx->fw_obj, &__entry->fw_addr); - __entry->hwrt_addr = job->hwrt ? - job->hwrt->fw_obj->fw_addr_offset : - 0; + __entry->hwrt_addr = PVR_JOB_GET_HWRT_FW_ADDR(job); __entry->job = job; __entry->job_type = job->type; __entry->sync_op_count = sync_op_count;), @@ -82,6 +91,7 @@ TRACE_EVENT(pvr_job_create, ); #undef PVR_JOB_TYPE_TO_STR +#undef PVR_JOB_GET_HWRT_FW_ADDR TRACE_EVENT(pvr_job_submit_fw, TP_PROTO(struct pvr_job *job), From a3caaa06809248b996254be5b47e10804a3494e2 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 16 Jul 2026 16:39:38 +0200 Subject: [PATCH 02/21] drm/panthor: reject firmware sections with oversized data In panthor_fw_load_section_entry(), the data size to copy is calculated without validating it against the allocated section_size: section->data.size = hdr.data.end - hdr.data.start; If a crafted firmware sets data.size larger than the allocated memory, this could cause a heap buffer overflow in panthor_fw_init_section_mem() memcpy(section->mem->kmap, section->data.buf, section->data.size); Additionally, if the section->data.size exceeds the BO size, could this memset underflow the size calculation, leading to a massive out-of-bounds zeroing of kernel memory? memset(section->mem->kmap + section->data.size, 0, panthor_kernel_bo_size(section->mem) - section->data.size); Reject section entries whose initial data is larger than the section size. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260716143939.21903-1-osama.abdelkader@gmail.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_fw.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index 90f59d782a80..0623d37ac617 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -545,6 +545,7 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev, struct panthor_fw_binary_section_entry_hdr hdr; struct panthor_fw_section *section; u32 section_size; + u32 data_size; u32 name_len; int ret; @@ -595,6 +596,13 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev, return -EINVAL; } + section_size = hdr.va.end - hdr.va.start; + data_size = hdr.data.end - hdr.data.start; + if (data_size > section_size) { + drm_err(&ptdev->base, "Firmware corrupted, section data exceeds section size\n"); + return -EINVAL; + } + name_len = iter->size - iter->offset; section = drmm_kzalloc(&ptdev->base, sizeof(*section), GFP_KERNEL); @@ -603,7 +611,7 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev, list_add_tail(§ion->node, &ptdev->fw->sections); section->flags = hdr.flags; - section->data.size = hdr.data.end - hdr.data.start; + section->data.size = data_size; if (section->data.size > 0) { void *data = drmm_kmalloc(&ptdev->base, section->data.size, GFP_KERNEL); @@ -626,7 +634,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev, section->name = name; } - section_size = hdr.va.end - hdr.va.start; if (section_size) { u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK; struct panthor_gem_object *bo; From 6a39ca1286dd97ad02bb8e03bbb65ee05368d778 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 17 Jul 2026 21:48:36 +0300 Subject: [PATCH 03/21] drm/bridge: display-connector: Fix I2C adapter resource leak If the probe function returns an error after getting the I2C adapter for DDC, the reference to the adapter is never released. Fix it by releasing it in the bridge .destroy() handler. There is no need to test the ddc pointer with !IS_ERR(), as of_get_i2c_adapter_by_node() returns NULL on error. Fixes: 2e2bf3a5584d ("drm/bridge: display-connector: add DP support") Cc: stable@vger.kernel.org Signed-off-by: Laurent Pinchart Reviewed-by: Johan Hovold Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260717184836.2017386-1-laurent.pinchart+renesas@ideasonboard.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/display-connector.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c index 16c0631adeb1..6b128fabe3a9 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -40,6 +40,13 @@ static int display_connector_attach(struct drm_bridge *bridge, return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL; } +static void display_connector_destroy(struct drm_bridge *bridge) +{ + struct display_connector *conn = to_display_connector(bridge); + + i2c_put_adapter(conn->bridge.ddc); +} + static enum drm_connector_status display_connector_detect(struct drm_bridge *bridge) { struct display_connector *conn = to_display_connector(bridge); @@ -177,6 +184,7 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge, static const struct drm_bridge_funcs display_connector_bridge_funcs = { .attach = display_connector_attach, + .destroy = display_connector_destroy, .detect = display_connector_bridge_detect, .edid_read = display_connector_edid_read, .atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts, @@ -403,9 +411,6 @@ static void display_connector_remove(struct platform_device *pdev) regulator_disable(conn->supply); drm_bridge_remove(&conn->bridge); - - if (!IS_ERR(conn->bridge.ddc)) - i2c_put_adapter(conn->bridge.ddc); } static const struct of_device_id display_connector_match[] = { From 83195b778f2d109a3a4f3ffaba4dce7e4cdb58aa Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:22 -0400 Subject: [PATCH 04/21] drm/vmwgfx: fix guest_memory_dirty bitfield clobbered as size Two sites in vmwgfx_resource.c assign boolean literals to res->guest_memory_size, which is an unsigned long allocation-size field; the intended target is the adjacent res->guest_memory_dirty bitfield. After the assignments the field holds 0 or 1 instead of the resource's MOB allocation size: - vmw_resource_release() writes 0 (false), and - vmw_resource_unbind_list() writes 1 (true). Subsequent revalidation paths read guest_memory_size when computing the dirty page range (vmw_bo_dirty_transfer_to_res()) and the buffer allocation size (vmw_resource_buf_alloc()), producing zero-length walks or wrap-around ranges that read or write past the MOB bitmap. The dirty-tracking intent of the original code (mark the resource as dirtied since the last sync) is also lost, since guest_memory_dirty is never updated. Rename both assignments to guest_memory_dirty. Fixes: 668b206601c5 ("drm/vmwgfx: Stop using raw ttm_buffer_object's") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-2-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c index 388011696941..e3a187a2c7a1 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c @@ -136,7 +136,7 @@ static void vmw_resource_release(struct kref *kref) val_buf.num_shared = 0; res->func->unbind(res, false, &val_buf); } - res->guest_memory_size = false; + res->guest_memory_dirty = false; vmw_resource_mob_detach(res); if (res->dirty) res->func->dirty_free(res); @@ -773,7 +773,7 @@ void vmw_resource_unbind_list(struct vmw_bo *vbo) if (!WARN_ON_ONCE(!res->func->unbind)) (void) res->func->unbind(res, res->res_dirty, &val_buf); - res->guest_memory_size = true; + res->guest_memory_dirty = true; res->res_dirty = false; vmw_resource_mob_detach(res); } From 55ec09c9ce10b1272802c7ab6c1be2ea0dbc68db Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:23 -0400 Subject: [PATCH 05/21] drm/vmwgfx: reject DX_BIND_QUERY without a DX context vmw_cmd_dx_bind_query() unconditionally dereferences sw_context->dx_ctx_node->ctx. Userspace can trigger a NULL pointer dereference from any render-node fd by submitting an execbuf with dx_context_handle == SVGA3D_INVALID_ID and a SVGA_3D_CMD_DX_BIND_QUERY opcode in the command stream: dx_ctx_node is left NULL and the kernel oopses on the assignment. The same NULL is then re-read in vmw_resources_reserve() via vmw_context_get_dx_query_mob(). All sibling DX handlers fail-close on a missing dx_ctx_node using VMW_GET_CTX_NODE(). Use the same pattern here, returning -EINVAL up front before any relocation state is published. Fixes: 9c079b8ce8bf ("drm/vmwgfx: Adapt execbuf to the new validation api") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-3-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c index e1f18020170a..b07f052474d0 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c @@ -1272,9 +1272,13 @@ static int vmw_cmd_dx_bind_query(struct vmw_private *dev_priv, SVGA3dCmdHeader *header) { VMW_DECLARE_CMD_VAR(*cmd, SVGA3dCmdDXBindQuery); + struct vmw_ctx_validation_info *ctx_node = VMW_GET_CTX_NODE(sw_context); struct vmw_bo *vmw_bo; int ret; + if (!ctx_node) + return -EINVAL; + cmd = container_of(header, typeof(*cmd), header); /* @@ -1288,7 +1292,7 @@ static int vmw_cmd_dx_bind_query(struct vmw_private *dev_priv, return ret; sw_context->dx_query_mob = vmw_bo; - sw_context->dx_query_ctx = sw_context->dx_ctx_node->ctx; + sw_context->dx_query_ctx = ctx_node->ctx; return 0; } From f47d542d5912f236273399d7139b522f6950e2e4 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:24 -0400 Subject: [PATCH 06/21] drm/vmwgfx: clamp dirty-page range with min, not max vmw_bo_dirty_transfer_to_res() and vmw_bo_dirty_clear() compute the intersection of a resource's page range with the BO's tracked dirty range, but clamp res_end against dirty->end with max() instead of min(). When dirty->end exceeds the resource end, the loop walks past the resource's pages, calls vmw_resource_dirty_update() for ranges owned by other resources sharing the same backing MOB and clears their pending dirty bits via bitmap_clear(). The result is silent loss of writeback for unrelated resources whenever two resources share a MOB. Use min() in both functions so the loop is bounded to the intersection of the resource and dirty ranges. Fixes: b7468b15d271 ("drm/vmwgfx: Implement an infrastructure for write-coherent resources") Fixes: 965544150d1c ("drm/vmwgfx: Refactor cursor handling") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-4-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c index 45561bc1c9ef..8ab88f388652 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c @@ -311,7 +311,7 @@ void vmw_bo_dirty_transfer_to_res(struct vmw_resource *res) return; cur = max(res_start, dirty->start); - res_end = max(res_end, dirty->end); + res_end = min(res_end, dirty->end); while (cur < res_end) { unsigned long num; @@ -347,7 +347,7 @@ void vmw_bo_dirty_clear(struct vmw_bo *vbo) return; cur = max(res_start, dirty->start); - res_end = max(res_end, dirty->end); + res_end = min(res_end, dirty->end); while (cur < res_end) { unsigned long num; From 250af2e8c3e90dc978e062a936b633870a22e660 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:25 -0400 Subject: [PATCH 07/21] drm/vmwgfx: take fman->lock around fence list mutation in fifo_down vmw_fence_fifo_down() drops fman->lock to wait on a fence and, on timeout, mutates fman->fence_list via list_del_init() and signals the fence without re-acquiring the lock. __vmw_fences_update() walks and removes entries from the same list under fman->lock from any other waiter, the fence-IRQ thread, or vmw_fences_update(), so the unlocked list_del_init() can corrupt the list head. Re-take fman->lock before manipulating fence->head and use dma_fence_signal_locked(). Wrap the locked signalling in dma_fence_begin_signalling() / dma_fence_end_signalling() so the lockdep annotation that dma_fence_signal() previously provided is preserved (the same pattern as __vmw_fences_update()). dma_fence_put() is moved outside the lock to avoid a recursive acquire from vmw_fence_obj_destroy(), which also takes fman->lock. Fixes: ae2a104058e2 ("vmwgfx: Implement fence objects") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-5-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_fence.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c index 4ef84ff9b638..384c6736cf6b 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c @@ -367,13 +367,24 @@ void vmw_fence_fifo_down(struct vmw_fence_manager *fman) ret = vmw_fence_obj_wait(fence, false, false, VMW_FENCE_WAIT_TIMEOUT); + spin_lock(&fman->lock); if (unlikely(ret != 0)) { + bool cookie = dma_fence_begin_signalling(); + list_del_init(&fence->head); - dma_fence_signal(&fence->base); + if (fence->waiter_added) { + vmw_seqno_waiter_remove(fman->dev_priv); + fence->waiter_added = false; + } + dma_fence_signal_locked(&fence->base); + dma_fence_end_signalling(cookie); } BUG_ON(!list_empty(&fence->head)); + spin_unlock(&fman->lock); + dma_fence_put(&fence->base); + spin_lock(&fman->lock); } spin_unlock(&fman->lock); From f739416dc555fa205a785e5135d73fa39b26f35d Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:26 -0400 Subject: [PATCH 08/21] drm/vmwgfx: drop dma_buf reference on foreign-fd prime import ttm_prime_fd_to_handle() returns -ENOSYS when the imported fd's dma_buf->ops do not match the ttm_object_device's ops, but does so without releasing the reference acquired by dma_buf_get(). Any unprivileged renderD client passing a non-vmwgfx prime fd through the DRM_VMW_GB_SURFACE_REF{,_EXT} path leaks one dma_buf reference per call and indefinitely pins the foreign exporter's GEM resources. Funnel the error path through the existing dma_buf_put() so the reference is always dropped. Fixes: 65981f7681ab ("drm/ttm: Add a minimal prime implementation for ttm base objects") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-6-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/ttm_object.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/ttm_object.c b/drivers/gpu/drm/vmwgfx/ttm_object.c index 2421b0dd057c..f9042bafdc93 100644 --- a/drivers/gpu/drm/vmwgfx/ttm_object.c +++ b/drivers/gpu/drm/vmwgfx/ttm_object.c @@ -547,14 +547,17 @@ int ttm_prime_fd_to_handle(struct ttm_object_file *tfile, if (IS_ERR(dma_buf)) return PTR_ERR(dma_buf); - if (dma_buf->ops != &tdev->ops) - return -ENOSYS; + if (dma_buf->ops != &tdev->ops) { + ret = -ENOSYS; + goto out; + } prime = (struct ttm_prime_object *) dma_buf->priv; base = &prime->base; *handle = base->handle; ret = ttm_ref_object_add(tfile, base, NULL, false); +out: dma_buf_put(dma_buf); return ret; From 85891d174707d8bddcec7a888fb4e1d17def34f3 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:27 -0400 Subject: [PATCH 09/21] drm/vmwgfx: validate DRAW_PRIMITIVES header size before division vmw_cmd_draw() computes maxnum = (header->size - sizeof(cmd->body)) / sizeof(*decl); where header->size is u32 and is taken straight from the user-supplied command stream. When header->size is less than sizeof(cmd->body) the unsigned subtraction wraps to nearly 4 GiB, producing a huge maxnum. Any user-controlled cmd->body.numVertexDecls then passes the bound and the loop dereferences decl[i] far past the end of the kernel command bounce buffer, producing an out-of-bounds read of kernel memory. Reject undersized headers up front. Fixes: 7a73ba7469cb ("drm/vmwgfx: Use TTM handles instead of SIDs as user-space surface handles.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-7-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c index b07f052474d0..2410d53a75aa 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c @@ -1571,11 +1571,17 @@ static int vmw_cmd_draw(struct vmw_private *dev_priv, uint32_t maxnum; int ret; + cmd = container_of(header, typeof(*cmd), header); + + if (unlikely(header->size < sizeof(cmd->body))) { + VMW_DEBUG_USER("Illegal DRAW_PRIMITIVES header size.\n"); + return -EINVAL; + } + ret = vmw_cmd_cid_check(dev_priv, sw_context, header); if (unlikely(ret != 0)) return ret; - cmd = container_of(header, typeof(*cmd), header); maxnum = (header->size - sizeof(cmd->body)) / sizeof(*decl); if (unlikely(cmd->body.numVertexDecls > maxnum)) { From f4f1db96bfd68b81053693ba53405b6f510ac16c Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:28 -0400 Subject: [PATCH 10/21] drm/vmwgfx: bound DMA command body size against suffix pointer vmw_cmd_dma() locates the DMA suffix at (unsigned long) &cmd->body + header->size - sizeof(*suffix) without checking that header->size is large enough to contain both cmd->body and the suffix. An undersized header makes the suffix pointer underflow back into the previous command in the bounce buffer. The verifier later writes suffix->maximumOffset, clobbering verified fields of an already-relocated earlier command -- a TOCTOU on the device-visible command stream that lets one command rewrite another's GMR id, surface id, or other authenticated fields. Reject the command if the body is too small for the suffix to fit. Fixes: 4e4ddd477743 ("drm/vmwgfx: Fix queries if no dma buffer thrashing is occuring.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-8-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c index 2410d53a75aa..a9136a6523cb 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c @@ -1510,6 +1510,12 @@ static int vmw_cmd_dma(struct vmw_private *dev_priv, bool dirty; cmd = container_of(header, typeof(*cmd), header); + + if (unlikely(header->size < sizeof(cmd->body) + sizeof(*suffix))) { + VMW_DEBUG_USER("Illegal SVGA_3D_CMD_SURFACE_DMA size.\n"); + return -EINVAL; + } + suffix = (SVGA3dCmdSurfaceDMASuffix *)((unsigned long) &cmd->body + header->size - sizeof(*suffix)); From 05eaa887e7b4f40fba425f8a1d7a5a8a043092a6 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:29 -0400 Subject: [PATCH 11/21] drm/vmwgfx: avoid destroy_workqueue(NULL) on vkms init failure Two paths through vmw_vkms_init() can leave vmw->crc_workq NULL while still leaving the rest of the driver in a state that calls vmw_vkms_cleanup() at module unload: 1. vmw_host_get_guestinfo(GUESTINFO_VBLANK, ...) failing or returning an oversized buffer -- the common case on hosts without a VBLANK guestinfo entry -- early-returned before the workqueue allocation. 2. alloc_ordered_workqueue() returning NULL on memory pressure. vmw_vkms_cleanup() then calls destroy_workqueue(NULL), which dereferences wq->name and panics. Fix the first case by removing the early return: vmw->vkms_enabled is already false on the rpci-failure path so no work will ever be queued, and allocating the workqueue unconditionally keeps the control flow simple. Fix the second case by guarding the cleanup with a NULL check, since alloc_ordered_workqueue() can still fail under low memory. Fixes: 7b0062036c3b ("drm/vmwgfx: Implement virtual crc generation") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-9-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c index 7b8163b5e501..3d0d5dfa869f 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c @@ -206,14 +206,14 @@ vmw_vkms_init(struct vmw_private *vmw) vmw->vkms_enabled = false; ret = vmw_host_get_guestinfo(GUESTINFO_VBLANK, buffer, &buf_len); - if (ret || buf_len > max_buf_len) - return; - buffer[buf_len] = '\0'; + if (!ret && buf_len <= max_buf_len) { + buffer[buf_len] = '\0'; - ret = kstrtobool(buffer, &vmw->vkms_enabled); - if (!ret && vmw->vkms_enabled) { - ret = drm_vblank_init(&vmw->drm, VMWGFX_NUM_DISPLAY_UNITS); - vmw->vkms_enabled = (ret == 0); + ret = kstrtobool(buffer, &vmw->vkms_enabled); + if (!ret && vmw->vkms_enabled) { + ret = drm_vblank_init(&vmw->drm, VMWGFX_NUM_DISPLAY_UNITS); + vmw->vkms_enabled = (ret == 0); + } } vmw->crc_workq = alloc_ordered_workqueue("vmwgfx_crc_generator", 0); @@ -228,7 +228,8 @@ vmw_vkms_init(struct vmw_private *vmw) void vmw_vkms_cleanup(struct vmw_private *vmw) { - destroy_workqueue(vmw->crc_workq); + if (vmw->crc_workq) + destroy_workqueue(vmw->crc_workq); } bool From d5ed8749168ad13c0dbaa8300f68d854b6076966 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:30 -0400 Subject: [PATCH 12/21] drm/vmwgfx: enforce cursor size limits for MOB cursors vmw_cursor_plane_atomic_check() bounds cursor width and height only on the legacy update path; the SVGA_CAP2_CURSOR_MOB path -- the default on modern hosts -- accepts any size. When the requested size exceeds SVGA_REG_CURSOR_MAX_DIMENSION or SVGA_REG_MOB_MAX_SIZE, vmw_cursor_mob_get() returns -EINVAL and leaves vps->cursor.mob NULL. Its return value is then discarded in vmw_cursor_plane_prepare_fb(), so the subsequent vmw_cursor_update_mob() calls vmw_bo_map_and_cache(NULL) and oopses inside vmw_bo_map_and_cache_size() on the tbo.base.size load. Reachable from any DRM master via DRM_IOCTL_MODE_CURSOR2 with a sufficiently large width or height (e.g. cursor_max_dim + 1). Reject oversized cursors in atomic_check for both MOB-backed cursor update types. The MOB byte-size limit only applies to the SVGA_CAP2_CURSOR_MOB path (vmw_cursor_mob_size() returns 0 for GB_ONLY); compute the required MOB size in 64-bit to avoid overflow when very large dimensions are requested. In prepare_fb only call vmw_cursor_mob_get()/_map() for VMW_CURSOR_UPDATE_MOB -- the GB_ONLY path uses bo->map.virtual directly and would otherwise be silently downgraded to NONE on hosts without SVGA_CAP2_CURSOR_MOB (where vmw_cursor_mob_get() always returns -EINVAL). Degrade the update to NONE if vmw_cursor_mob_get() or vmw_cursor_mob_map() fails so the update path does not run with a NULL backing MOB. Fixes: 965544150d1c ("drm/vmwgfx: Refactor cursor handling") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-10-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c | 49 ++++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c index b010fc7ca68e..d1e7df500190 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c @@ -432,6 +432,7 @@ vmw_cursor_mob_map(struct vmw_plane_state *vps) u32 size = vmw_cursor_mob_size(vps->cursor.update_type, vps->base.crtc_w, vps->base.crtc_h); struct vmw_bo *vbo = vps->cursor.mob; + void *map; if (!vbo) return -EINVAL; @@ -446,11 +447,15 @@ vmw_cursor_mob_map(struct vmw_plane_state *vps) if (unlikely(ret != 0)) return -ENOMEM; - vmw_bo_map_and_cache(vbo); + map = vmw_bo_map_and_cache(vbo); + if (!map) { + vmw_bo_unmap(vbo); + ret = -ENOMEM; + } ttm_bo_unreserve(&vbo->tbo); - return 0; + return ret; } /** @@ -663,9 +668,15 @@ int vmw_cursor_plane_prepare_fb(struct drm_plane *plane, !vmw_cursor_buffer_changed(vps, old_vps)) { vps->cursor.update_type = VMW_CURSOR_UPDATE_NONE; - } else { - vmw_cursor_mob_get(vcp, vps); - vmw_cursor_mob_map(vps); + } else if (vps->cursor.update_type == + VMW_CURSOR_UPDATE_MOB && + (vmw_cursor_mob_get(vcp, vps) || + vmw_cursor_mob_map(vps))) { + /* + * Reset the cursor to avoid crashes later. + */ + vps->cursor.update_type = + VMW_CURSOR_UPDATE_NONE; } } } @@ -732,6 +743,34 @@ int vmw_cursor_plane_atomic_check(struct drm_plane *plane, "surface not suitable for cursor\n"); return -EINVAL; } + } else if (update_type == VMW_CURSOR_UPDATE_GB_ONLY || + update_type == VMW_CURSOR_UPDATE_MOB) { + u32 cursor_max_dim = + vmw_read(vmw, SVGA_REG_CURSOR_MAX_DIMENSION); + + if (new_state->crtc_w > cursor_max_dim || + new_state->crtc_h > cursor_max_dim) { + drm_warn(&vmw->drm, + "Cursor dimensions (%d, %d) exceed device max %u\n", + new_state->crtc_w, new_state->crtc_h, + cursor_max_dim); + return -EINVAL; + } + + if (update_type == VMW_CURSOR_UPDATE_MOB) { + u32 mob_max_size = + vmw_read(vmw, SVGA_REG_MOB_MAX_SIZE); + u64 mob_size = (u64)new_state->crtc_w * + new_state->crtc_h * sizeof(u32) + + sizeof(SVGAGBCursorHeader); + + if (mob_size > mob_max_size) { + drm_warn(&vmw->drm, + "Cursor MOB size %llu exceeds device max %u\n", + mob_size, mob_max_size); + return -EINVAL; + } + } } return 0; From e5c3e484e0d84744a9bd9349469cd41dc8666a2f Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:31 -0400 Subject: [PATCH 13/21] drm/vmwgfx: skip hash_del_rcu when validation context has no hash table vmw_validation_add_resource() calls hash_add_rcu() only when ctx->sw_context is non-NULL, but the doomed-resource error path calls hash_del_rcu() unconditionally. The validation contexts declared with DECLARE_VAL_CONTEXT(_, NULL, 0) in vmwgfx_kms.c, vmwgfx_scrn.c, vmwgfx_stdu.c and vmwgfx_execbuf.c consequently reach a delete for a node that was never added to any hash chain. That is harmless today, but only incidentally so. hash_del_rcu() is hlist_del_init_rcu(), which is guarded by hlist_unhashed(), and vmw_validation_mem_alloc() hands out memory from __GFP_ZERO pages that are never recycled within a context's lifetime, so node->hash.head.pprev is always NULL and the delete does nothing. Neither property is apparent at the call site, and the asymmetry with the add side invites a real bug the first time either one changes. Mirror the condition from the add side so the node is only unlinked when it was actually linked. No functional change. Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-11-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_validation.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c index 35dc94c3db39..45fde7ec514f 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_validation.c @@ -309,7 +309,8 @@ int vmw_validation_add_resource(struct vmw_validation_context *ctx, } node->res = vmw_resource_reference_unless_doomed(res); if (!node->res) { - hash_del_rcu(&node->hash.head); + if (ctx->sw_context) + hash_del_rcu(&node->hash.head); return -ESRCH; } From 54d56d5b42d2e4c72ba6e365e9774da90698aa22 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:32 -0400 Subject: [PATCH 14/21] drm/vmwgfx: use check_add_overflow for shader size+offset bound vmw_shader_define() validates the user-supplied shader window against its backing buffer with (u64)buffer->tbo.base.size < (u64)size + (u64)offset drm_vmw_shader_create_arg::offset is __u64 in the uapi; when it is near U64_MAX the unsigned addition wraps and the resulting tiny value passes the check. The unbounded offset is then stored in res->guest_memory_offset and forwarded to host SVGA shader-create commands. Use check_add_overflow() to detect the wrap and compare the resulting endpoint against the buffer size. Fixes: 668b206601c5 ("drm/vmwgfx: Stop using raw ttm_buffer_object's") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-12-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_shader.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c b/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c index eca4e3e97eb4..39811cf19db1 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c @@ -25,6 +25,8 @@ * **************************************************************************/ +#include + #include #include "vmwgfx_binding.h" @@ -685,7 +687,7 @@ int vmw_shader_destroy_ioctl(struct drm_device *dev, void *data, static int vmw_user_shader_alloc(struct vmw_private *dev_priv, struct vmw_bo *buffer, size_t shader_size, - size_t offset, + u64 offset, SVGA3dShaderType shader_type, uint8_t num_input_sig, uint8_t num_output_sig, @@ -739,7 +741,7 @@ static int vmw_user_shader_alloc(struct vmw_private *dev_priv, static struct vmw_resource *vmw_shader_alloc(struct vmw_private *dev_priv, struct vmw_bo *buffer, size_t shader_size, - size_t offset, + u64 offset, SVGA3dShaderType shader_type) { struct vmw_shader *shader; @@ -768,7 +770,7 @@ static struct vmw_resource *vmw_shader_alloc(struct vmw_private *dev_priv, static int vmw_shader_define(struct drm_device *dev, struct drm_file *file_priv, enum drm_vmw_shader_type shader_type_drm, - u32 buffer_handle, size_t size, size_t offset, + u32 buffer_handle, size_t size, u64 offset, uint8_t num_input_sig, uint8_t num_output_sig, uint32_t *shader_handle) { @@ -779,13 +781,16 @@ static int vmw_shader_define(struct drm_device *dev, struct drm_file *file_priv, int ret; if (buffer_handle != SVGA3D_INVALID_ID) { + u64 end; + ret = vmw_user_bo_lookup(file_priv, buffer_handle, &buffer); if (unlikely(ret != 0)) { VMW_DEBUG_USER("Couldn't find buffer for shader creation.\n"); return ret; } - if ((u64)buffer->tbo.base.size < (u64)size + (u64)offset) { + if (check_add_overflow((u64)size, (u64)offset, &end) || + end > buffer->tbo.base.size) { VMW_DEBUG_USER("Illegal buffer- or shader size.\n"); ret = -EINVAL; goto out_bad_arg; From 706c93c5813caabbb0d0a576c017d15aeec2c113 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Tue, 5 May 2026 18:22:33 -0400 Subject: [PATCH 15/21] 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: 50f119925091 ("drm/vmwgfx: Fix prime with external buffers") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Zack Rusin Reviewed-by: Ian Forbes Link: https://patch.msgid.link/20260505222728.519626-13-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_blit.c | 39 ++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c b/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c index 135b75a3e013..56f965ec99dc 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c @@ -30,6 +30,7 @@ #include "vmwgfx_bo.h" #include +#include /* * 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; } From 6395789e4739aa5177bbec0fa0f07ccc38d249b0 Mon Sep 17 00:00:00 2001 From: Jose Maria Casanova Crespo Date: Mon, 27 Jul 2026 11:32:28 -0300 Subject: [PATCH 16/21] drm/vc4: Supply the overflow slot size in BPOS, not the whole bin BO size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_overflow_mem_work() points BPOA at a 512KB slot inside the 16MB binner BO, but writes the size of the whole BO to BPOS. On every binner out-of-memory event the PTB is therefore authorized to write tile lists across all the other slots (which may hold the tile state, tile alloc and overflow memory of in-flight jobs) and, for any slot but the first, past the end of the binner BO into unrelated CMA memory. Since CMA pages are recycled into page cache and user allocations, this is arbitrary memory corruption by GPU DMA. In practice it shows up as GPU hangs with corrupted control list pointers, userspace heap corruption, a GPU that stays permanently wedged after the first hang, and occasional full system crashes, whenever a job overflows the initial binner slot. The bug dates back to the conversion from a dedicated overflow BO (where writing the full BO size was correct) to the slotted binner BO. Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Jose Maria Casanova Crespo Reviewed-by: Maíra Canal Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260727-vc4-bin-oom-fixes-v2-1-0d8a5eddc7c9@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 8e5141bb5075..7877d493d80e 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -104,7 +104,7 @@ vc4_overflow_mem_work(struct work_struct *work) vc4->bin_alloc_overflow = BIT(bin_bo_slot); V3D_WRITE(V3D_BPOA, bo->base.dma_addr + bin_bo_slot * vc4->bin_alloc_size); - V3D_WRITE(V3D_BPOS, bo->base.base.size); + V3D_WRITE(V3D_BPOS, vc4->bin_alloc_size); V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM); V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM); spin_unlock_irqrestore(&vc4->job_lock, irqflags); From 48a570c964d8e37d353381e4195106277e17f5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Mon, 27 Jul 2026 11:32:29 -0300 Subject: [PATCH 17/21] drm/vc4: Zero the tile state data array before each BIN job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binner BO is a single 16MB buffer split into 512KB slots that are handed out to jobs at submission time and recycled as jobs complete, without ever being cleared. Each slot holds the job's Tile State Data Array (TSDA) at its start, followed by the tile allocation pool. While the tile allocation pool is only walked by the render thread through branches the binner generated during the current job, the TSDA is the PTB's own per-tile bookkeeping and is consumed by the hardware itself. Although the kernel sets the "Auto-initialise Tile State Data Array" flag in the tile binning mode configuration, the PTB demonstrably still acts on stale tile state left by the slot's previous user: the binner ends up creating invalid command streams with invalid primitive streams and branches, which can cause GPU hangs as observed in [1][2]. Zero the TSDA when the job's binning slot is configured. This clears 48 bytes per tile (~24KB for a 1080p frame) in the submission path, and guarantees the PTB never sees another job's tile state. The tile count is only checked for being non-zero today, so the 8-bit fields it comes from can describe a tile state array almost six times larger than the slot it has to live in. Bound it before the slot is handed out, since such size decides how much of the slot is left for the tile alloc pool. Link: https://github.com/raspberrypi/linux/issues/3221 [1] Link: https://github.com/raspberrypi/linux/issues/5780 [2] Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Cc: stable@vger.kernel.org Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260727-vc4-bin-oom-fixes-v2-2-0d8a5eddc7c9@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_validate.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index 7f2fadfde7a8..d2a65c968b1f 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -385,6 +385,23 @@ validate_tile_binning_config(VALIDATE_ARGS) return -EINVAL; } + /* The tile state data array is 48 bytes per tile, and we put it at + * the start of a BO containing both it and the tile alloc. + */ + tile_state_size = 48 * tile_count; + + /* Since the tile alloc array will follow us, align. */ + tile_state_size = roundup(tile_state_size, 4096); + + /* Reject configurations whose tile state would leave no room for + * the tile alloc pool that follows it in the slot. + */ + if (tile_state_size >= vc4->bin_alloc_size) { + DRM_DEBUG("Tile binning config of %dx%d too large\n", + exec->bin_tiles_x, exec->bin_tiles_y); + return -EINVAL; + } + bin_slot = vc4_v3d_get_bin_slot(vc4); if (bin_slot < 0) { if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) { @@ -400,13 +417,13 @@ validate_tile_binning_config(VALIDATE_ARGS) exec->bin_slots |= BIT(bin_slot); bin_addr = vc4->bin_bo->base.dma_addr + bin_slot * vc4->bin_alloc_size; - /* The tile state data array is 48 bytes per tile, and we put it at - * the start of a BO containing both it and the tile alloc. - */ - tile_state_size = 48 * tile_count; + exec->tile_alloc_offset = bin_addr + tile_state_size; - /* Since the tile alloc array will follow us, align. */ - exec->tile_alloc_offset = bin_addr + roundup(tile_state_size, 4096); + /* The TSDA area must be zeroed out before use, otherwise the PTB might + * consume a stale tile state. + */ + memset(vc4->bin_bo->base.vaddr + bin_slot * vc4->bin_alloc_size, 0, + tile_state_size); *(uint8_t *)(validated + 14) = ((flags & ~(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_MASK | From e40e20ac089e32f1d910636155dc82e61e61dcf3 Mon Sep 17 00:00:00 2001 From: Alexander Kaplan Date: Wed, 10 Jun 2026 21:38:25 +0200 Subject: [PATCH 18/21] drm/dp: Read the PCON max FRL bandwidth only for HDMI DFPs The PCON max FRL bandwidth field lives in byte 2 of the DFP Detailed Capability Info (DPCD 0x82 for the first DFP). The DP standard defines the meaning of descriptor bytes 1-3 strictly per DFP type, and for a DisplayPort type DFP all of them are reserved, with "read all 0s" semantics (DP v2.0, section 2.12.3, Table 2-183). The FRL bandwidth field is an HDMI DFP extension added by the VESA DP-to-HDMI PCON specification. drm_dp_get_pcon_max_frl_bw() however parses the byte without checking the DFP type, the branch presence or DETAILED_CAP_INFO_AVAILABLE. Without the latter the port descriptors are one byte wide and port_cap[2] is not even the right register. All neighbouring helpers parsing the same descriptor are scoped by the DFP type already, see for instance drm_dp_downstream_max_bpc() reading the same byte and returning 0 for a DP type DFP. amdgpu's DC parses the field only for HDMI(/DP++) detailed types as well. This is not theoretical. A Synaptics VMM7100 based USB-C to HDMI adapter with a macOS targeted firmware advertises a DisplayPort type DFP with the type byte replicated across the whole descriptor (08 08 08 08). i915 decodes that as "PCON limited to 18 Gbps FRL" and prunes every mode above ~750 MHz dotclock, including all the 4k@100/120 modes the sink EDID offers, while macOS drives 4k@120 through the same adapter just fine via DP DSC (and amdgpu's type-scoped parser would ignore the bogus field as well). Only parse the field for an HDMI DFP behind a DPCD 1.1+ branch device that reports detailed cap info, matching the type-scoped field layout of the spec and the rest of the helpers. Fixes: ce32a6239de6 ("drm/dp_helper: Add Helpers for FRL Link Training support for DP-HDMI2.1 PCON") Cc: Ankit Nautiyal Cc: Uma Shankar (v2) Cc: Jani Nikula Cc: Maarten Lankhorst Cc: dri-devel@lists.freedesktop.org Cc: # v5.12+ Signed-off-by: Alexander Kaplan Reviewed-by: Ankit Nautiyal Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260610193825.2933-1-alexander.kaplan@sms-medipool.de --- drivers/gpu/drm/display/drm_dp_helper.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index 9c31e14cc413..e623ccb4c1d8 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -3686,6 +3686,18 @@ int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], int bw; u8 buf; + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) + return 0; + + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 0; + + if ((port_cap[0] & DP_DS_PORT_TYPE_MASK) != DP_DS_PORT_TYPE_HDMI) + return 0; + buf = port_cap[2]; bw = buf & DP_PCON_MAX_FRL_BW; From c256bd486855d8948aff29e0c97d50712da4e85e Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Fri, 24 Jul 2026 20:12:12 +0530 Subject: [PATCH 19/21] drm/imagination: Update the trace point pvr_job_submit_fw() Trace point pvr_job_submit_fw() is used to trace job submission to the FW. Currently it is recorded when a command is written to the Client circular buffer. Move trace recording after writing command to the Kernel circular buffer to better represent command submission to the FW. Fixes: c1079aebb4de ("drm/imagination: Add support for trace points") Signed-off-by: Brajesh Gupta Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260724-b4-tracepoint-fix-v3-1-8f8e5e8179d3@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_queue.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index 941c017399fc..d13726401d92 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -726,8 +726,6 @@ static void pvr_queue_submit_job_to_cccb(struct pvr_job *job) cmd->partial_render_geom_frag_fence.value = job->done_fence->seqno - 1; } - trace_pvr_job_submit_fw(job); - /* Submit job to FW */ pvr_cccb_write_command_with_header(cccb, job->fw_ccb_cmd_type, job->cmd_len, job->cmd, job->id, job->id); @@ -802,6 +800,9 @@ static struct dma_fence *pvr_queue_run_job(struct drm_sched_job *sched_job) job->hwrt, frag_job->fw_ccb_cmd_type == ROGUE_FWIF_CCB_CMD_TYPE_FRAG_PR); + + trace_pvr_job_submit_fw(geom_job); + trace_pvr_job_submit_fw(frag_job); } else { struct pvr_queue *queue = container_of(job->base.sched, struct pvr_queue, scheduler); @@ -809,6 +810,8 @@ static struct dma_fence *pvr_queue_run_job(struct drm_sched_job *sched_job) pvr_cccb_send_kccb_kick(pvr_dev, &queue->cccb, pvr_context_get_fw_addr(job->ctx) + queue->ctx_offset, job->hwrt); + + trace_pvr_job_submit_fw(job); } return dma_fence_get(job->done_fence); From b921b8613790a3f9e78ab64017fa7149ef0b750c Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Mon, 20 Jul 2026 13:49:17 +0200 Subject: [PATCH 20/21] drm/panthor: validate firmware interface structure sizes iface_fw_to_cpu_addr() only checks that the firmware-provided MCU virtual address points inside the shared section. The returned pointer is later used as a full firmware interface structure, so accepting an address near the end of the shared section can still lead to out-of-bounds accesses. Pass the expected object size to iface_fw_to_cpu_addr() and reject ranges that do not fit entirely in the shared section. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patch.msgid.link/20260720114918.15973-1-osama.abdelkader@gmail.com --- drivers/gpu/drm/panthor/panthor_fw.c | 36 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index 0623d37ac617..e45933e333b5 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -864,18 +864,24 @@ static int panthor_fw_load(struct panthor_device *ptdev) * iface_fw_to_cpu_addr() - Turn an MCU address into a CPU address * @ptdev: Device. * @mcu_va: MCU address. + * @size: Size of the object pointed to by @mcu_va. * - * Return: NULL if the address is not part of the shared section, non-NULL otherwise. + * Return: NULL if the object is not part of the shared section, non-NULL otherwise. */ -static void *iface_fw_to_cpu_addr(struct panthor_device *ptdev, u32 mcu_va) +static void *iface_fw_to_cpu_addr(struct panthor_device *ptdev, u32 mcu_va, size_t size) { u64 shared_mem_start = panthor_kernel_bo_gpuva(ptdev->fw->shared_section->mem); - u64 shared_mem_end = shared_mem_start + - panthor_kernel_bo_size(ptdev->fw->shared_section->mem); - if (mcu_va < shared_mem_start || mcu_va >= shared_mem_end) + size_t shared_mem_size = panthor_kernel_bo_size(ptdev->fw->shared_section->mem); + u64 offset; + + if (mcu_va < shared_mem_start) return NULL; - return ptdev->fw->shared_section->mem->kmap + (mcu_va - shared_mem_start); + offset = mcu_va - shared_mem_start; + if (offset > shared_mem_size || size > shared_mem_size - offset) + return NULL; + + return ptdev->fw->shared_section->mem->kmap + offset; } static int panthor_init_cs_iface(struct panthor_device *ptdev, @@ -897,8 +903,10 @@ static int panthor_init_cs_iface(struct panthor_device *ptdev, spin_lock_init(&cs_iface->lock); cs_iface->control = ptdev->fw->shared_section->mem->kmap + iface_offset; - cs_iface->input = iface_fw_to_cpu_addr(ptdev, cs_iface->control->input_va); - cs_iface->output = iface_fw_to_cpu_addr(ptdev, cs_iface->control->output_va); + cs_iface->input = iface_fw_to_cpu_addr(ptdev, cs_iface->control->input_va, + sizeof(*cs_iface->input)); + cs_iface->output = iface_fw_to_cpu_addr(ptdev, cs_iface->control->output_va, + sizeof(*cs_iface->output)); if (!cs_iface->input || !cs_iface->output) { drm_err(&ptdev->base, "Invalid stream control interface input/output VA"); @@ -948,8 +956,10 @@ static int panthor_init_csg_iface(struct panthor_device *ptdev, spin_lock_init(&csg_iface->lock); csg_iface->control = ptdev->fw->shared_section->mem->kmap + iface_offset; - csg_iface->input = iface_fw_to_cpu_addr(ptdev, csg_iface->control->input_va); - csg_iface->output = iface_fw_to_cpu_addr(ptdev, csg_iface->control->output_va); + csg_iface->input = iface_fw_to_cpu_addr(ptdev, csg_iface->control->input_va, + sizeof(*csg_iface->input)); + csg_iface->output = iface_fw_to_cpu_addr(ptdev, csg_iface->control->output_va, + sizeof(*csg_iface->output)); if (csg_iface->control->stream_num < MIN_CS_PER_CSG || csg_iface->control->stream_num > MAX_CS_PER_CSG) @@ -1006,8 +1016,10 @@ static int panthor_fw_init_ifaces(struct panthor_device *ptdev) return -EINVAL; } - glb_iface->input = iface_fw_to_cpu_addr(ptdev, glb_iface->control->input_va); - glb_iface->output = iface_fw_to_cpu_addr(ptdev, glb_iface->control->output_va); + glb_iface->input = iface_fw_to_cpu_addr(ptdev, glb_iface->control->input_va, + sizeof(*glb_iface->input)); + glb_iface->output = iface_fw_to_cpu_addr(ptdev, glb_iface->control->output_va, + sizeof(*glb_iface->output)); if (!glb_iface->input || !glb_iface->output) { drm_err(&ptdev->base, "Invalid global control interface input/output VA"); return -EINVAL; From d6c075f797a672a6e3bd2fd44aee713801698ec2 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Thu, 18 Jun 2026 02:25:20 +0500 Subject: [PATCH 21/21] accel/qaic: use sizeof(*trans_hdr) for transaction length check In encode_message() the per-transaction lower-bound check compares trans_hdr->len against sizeof(trans_hdr), i.e. the size of the pointer, instead of sizeof(*trans_hdr), the size of struct qaic_manage_trans_hdr. Every other length check in this file (encode_message() at the loop guard, decode_message(), etc.) correctly uses sizeof(*trans_hdr), so this is an inconsistency. On 64-bit builds the pointer and the struct are both 8 bytes, so the check is correct by coincidence and there is no behavioural change. On 32-bit builds the pointer is 4 bytes, which weakens the minimum-length check below the 8-byte header size. Use sizeof(*trans_hdr) so the check validates against the actual transaction header size on all builds. Fixes: ea33cb6fc278 ("accel/qaic: tighten bounds checking in encode_message()") Signed-off-by: Muhammad Bilal Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260617212520.59801-1-meatuni001@gmail.com --- drivers/accel/qaic/qaic_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/qaic/qaic_control.c b/drivers/accel/qaic/qaic_control.c index bb94d3556904..50bf3340e49c 100644 --- a/drivers/accel/qaic/qaic_control.c +++ b/drivers/accel/qaic/qaic_control.c @@ -786,7 +786,7 @@ static int encode_message(struct qaic_device *qdev, struct manage_msg *user_msg, break; } trans_hdr = (struct qaic_manage_trans_hdr *)(user_msg->data + user_len); - if (trans_hdr->len < sizeof(trans_hdr) || + if (trans_hdr->len < sizeof(*trans_hdr) || size_add(user_len, trans_hdr->len) > user_msg->len) { ret = -EINVAL; break;