CREATE_QUEUE takes an unprivileged, only lower-bounded ctx_save_restore_area_size. kfd_queue_acquire_buffers() computes the total CWSR size as (ctx_save_restore_area_size + debug_memory_size) * NUM_XCC() in a u32 with no overflow check. On multi-XCC parts (NUM_XCC up to 8) a large size wraps to a small value, so an undersized BO passes validation while init_mqd_v9_4_3() still strides by the raw size -- a GPU-side out-of-bounds write from an unprivileged /dev/kfd caller.
Compute the total with check_add/mul_overflow() and reject on overflow, and bound-check the per-XCC CWSR stride in the MQD managers. Signed-off-by: Yongqiang Sun <[email protected]> --- .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 8 ++- .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 8 ++- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 31 ++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 49 ++++++++++++++++--- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index a6fff8032dce..d45b3fc3baec 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -473,7 +473,8 @@ static void init_mqd_v12_1(struct mqd_manager *mm, void **mqd, * and CWSR area is allocated in thunk */ if (mm->dev->kfd->cwsr_enabled && - q->ctx_save_restore_area_address) { + q->ctx_save_restore_area_address && + !WARN_ON_ONCE(!kfd_queue_cwsr_xcc_in_bounds(q, xcc))) { xcc_ctx_save_restore_area_address = q->ctx_save_restore_area_address + (xcc * q->ctx_save_restore_area_size); @@ -596,6 +597,11 @@ static int get_wave_state_v12_1(struct mqd_manager *mm, void *mqd, u32 tmp_ctl_stack_used_size = 0, tmp_save_area_used_size = 0; for (xcc = 0; xcc < NUM_XCC(mm->dev->xcc_mask); xcc++) { + if (WARN_ON_ONCE(!kfd_queue_cwsr_xcc_in_bounds(q, xcc))) { + err = -EINVAL; + break; + } + xcc_mqd = mqd + mqd_stride_size * xcc; xcc_ctl_stack = (void __user *)((uintptr_t)ctl_stack + q->ctx_save_restore_area_size * xcc); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index 415e9d8130cf..0dc5e6acb5dc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -737,7 +737,8 @@ static void init_mqd_v9_4_3(struct mqd_manager *mm, void **mqd, * and CWSR area is allocated in thunk */ if (mm->dev->kfd->cwsr_enabled && - q->ctx_save_restore_area_address) { + q->ctx_save_restore_area_address && + !WARN_ON_ONCE(!kfd_queue_cwsr_xcc_in_bounds(q, xcc))) { xcc_ctx_save_restore_area_address = q->ctx_save_restore_area_address + (xcc * q->ctx_save_restore_area_size); @@ -928,6 +929,11 @@ static int get_wave_state_v9_4_3(struct mqd_manager *mm, void *mqd, u32 tmp_ctl_stack_used_size = 0, tmp_save_area_used_size = 0; for (xcc = 0; xcc < NUM_XCC(mm->dev->xcc_mask); xcc++) { + if (WARN_ON_ONCE(!kfd_queue_cwsr_xcc_in_bounds(q, xcc))) { + err = -EINVAL; + break; + } + xcc_mqd = mqd + mqd_stride_size * xcc; xcc_ctl_stack = (void __user *)((uintptr_t)ctl_stack + q->ctx_save_restore_area_size * xcc); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 6e559aab4009..ef6d1c8aa780 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -541,6 +541,14 @@ struct queue_properties { uint32_t eop_ring_buffer_size; uint64_t ctx_save_restore_area_address; uint32_t ctx_save_restore_area_size; + /* + * Total, overflow-checked size of the CWSR buffer across all XCCs as + * validated against the pinned BO in kfd_queue_acquire_buffers(). The + * MQD managers use this as an upper bound when programming the per-XCC + * CWSR save base so the raw per-XCC stride can never index past the + * validated allocation. + */ + uint32_t cwsr_area_total_size; uint32_t ctl_stack_size; uint64_t tba_addr; uint64_t tma_addr; @@ -553,6 +561,29 @@ struct queue_properties { struct amdgpu_bo *cwsr_bo; }; +/* + * Bound-check a per-XCC CWSR save area against the validated total CWSR buffer + * size. The MQD managers program the per-XCC CWSR base using the raw + * ctx_save_restore_area_size as the stride; this confirms the save area for + * @xcc lies within the allocation that kfd_queue_acquire_buffers() validated. + * + * Returns true when the access is in bounds. A false return indicates a kernel + * bug (the overflow-checked validation in kfd_queue_acquire_buffers() should + * have already rejected the queue), and the caller must not program an + * out-of-bounds CWSR base. When no validated size was recorded + * (cwsr_area_total_size == 0, e.g. non-CWSR queues) the legacy behavior is + * preserved. + */ +static inline bool kfd_queue_cwsr_xcc_in_bounds(const struct queue_properties *q, + unsigned int xcc) +{ + if (!q->cwsr_area_total_size) + return true; + + return (u64)(xcc + 1) * q->ctx_save_restore_area_size <= + q->cwsr_area_total_size; +} + #define QUEUE_IS_ACTIVE(q) ((q).queue_size > 0 && \ (q).queue_address != 0 && \ (q).queue_percent > 0 && \ diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 9d4838461168..6bb7aa0fdf1a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -23,6 +23,7 @@ */ #include <linux/slab.h> +#include <linux/overflow.h> #include "kfd_priv.h" #include "kfd_topology.h" #include "kfd_svm.h" @@ -230,6 +231,40 @@ void kfd_queue_buffer_put(struct amdgpu_bo **bo) amdgpu_bo_unref(bo); } +/* + * Compute the total CWSR buffer size across all XCCs. + * + * The per-XCC size and the NUM_XCC multiply are both attacker-influenced + * (ctx_save_restore_area_size comes straight from the CREATE_QUEUE ioctl and + * is only lower-bounded), so the arithmetic must be overflow-checked. A wrapped + * result would let an undersized BO pass the exact-match validation in + * kfd_queue_buffer_get() while the per-XCC CWSR hardware stride still uses the + * raw user size, causing a GPU-side out-of-bounds write on multi-XCC parts. + */ +static int kfd_queue_compute_cwsr_size(struct kfd_process_device *pdd, + struct kfd_topology_device *topo_dev, + struct queue_properties *properties, + u32 *total_cwsr_size) +{ + u32 per_xcc_size; + + if (check_add_overflow(properties->ctx_save_restore_area_size, + topo_dev->node_props.debug_memory_size, + &per_xcc_size)) + return -EINVAL; + + if (check_mul_overflow(per_xcc_size, NUM_XCC(pdd->dev->xcc_mask), + total_cwsr_size)) + return -EINVAL; + + /* PAGE_ALIGN() must not wrap either. */ + if (*total_cwsr_size > U32_MAX - (PAGE_SIZE - 1)) + return -EINVAL; + *total_cwsr_size = ALIGN(*total_cwsr_size, PAGE_SIZE); + + return 0; +} + int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; @@ -308,9 +343,12 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope goto out_err_unreserve; } - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); - total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); + err = kfd_queue_compute_cwsr_size(pdd, topo_dev, properties, &total_cwsr_size); + if (err) + goto out_err_unreserve; + + /* Carry the validated size to the MQD managers as a per-XCC bound. */ + properties->cwsr_area_total_size = total_cwsr_size; err = kfd_queue_buffer_get(vm, (void *)properties->ctx_save_restore_area_address, &properties->cwsr_bo, total_cwsr_size); @@ -355,9 +393,8 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) return -EINVAL; - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); - total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); + if (kfd_queue_compute_cwsr_size(pdd, topo_dev, properties, &total_cwsr_size)) + return -EINVAL; kfd_queue_buffer_svm_put(pdd, properties->ctx_save_restore_area_address, total_cwsr_size); return 0; -- 2.43.0
