From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qv1-f47.google.com (mail-qv1-f47.google.com [209.85.219.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 06A542882DE for ; Sun, 14 Jun 2026 21:17:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.47 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781471837; cv=none; b=alE/MkTdGxt/4RKirSXcCCHlReV2zIeOeS996lka4cR9YVvSFXw0q/lTMnXSgwXvH5yaNr9TZnf9kIVBSfjpbAyIb87ewhqiwVvmPJmRke8ZeZP5wzFTdnWR5OxWfpkV80pLdMku/GkB9VyFQSHnJoXSYqD47xO30V92LN2CttU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781471837; c=relaxed/simple; bh=yyHNfiUwqD+lcx5NDWNT5Aub5y67d5W63vebYO8zR7o=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Tu/KaC+L59J5C4uq3xKyJNbDb6TgoluviAc71QNPdhbxMcbfQ5CeeZ7jNn9egO6oiupk8WIGFrgtN/6Cy7mInsHrBJqYj/XcTU4Y5U3pa9LfXy9+JO2RG8MvhLAhOZ7wIHaXoGTDH7zTC6GR1XfhslhSpHQB3R6eNFh66VkTrT8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=InCmrwCg; arc=none smtp.client-ip=209.85.219.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="InCmrwCg" Received: by mail-qv1-f47.google.com with SMTP id 6a1803df08f44-8ccef9eabccso38997056d6.1 for ; Sun, 14 Jun 2026 14:17:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781471835; x=1782076635; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=XxZ+Wx6zViR4ZOtaE9BQrIto682T5tB19z5D7ua0ues=; b=InCmrwCgO0kllVgWx6byxmIMFJK5HWrsF14hGCC78NYmOA2NP3XQHiWEl7KGO6+uM4 VG9H0Q0FZkUua7vGe9gNkXBvQpxl5lZvx7aNpQmdGNl9SHzvxN8ZNHV3GLTsDZ3AtmvO V3xaGKbjG3+RSpUYg5z4f2ghUGxXpYtFFqAUWhmnAIyXh0poxT5lNeyZN/z+KvHkGWzc 3rBzfR71/BOxzf1GauGHmJlqw0SP7BsmjATNB6LY4NetAAWEUZQDf+5k6L3xnvJ+S6iX pA80IMcI+aJwfomJxkhFR0JONgy5vmUqJ7Z7IQ6G7/NrwZgrQLrATcFTh4x6Sa6IBnHk wHZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781471835; x=1782076635; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=XxZ+Wx6zViR4ZOtaE9BQrIto682T5tB19z5D7ua0ues=; b=XVckf2MMiIWJeSs6FbQjMkDFY4cYpfH/JZMkk9IL1wfBmGx6bjn9KBUmSN9EC5A4Ru mjzKdHQMwosE+oNi2cBMknu6fTrrjGy1oSySstHwTcRbVkOi0BmELhKhIvmNVplyJyDe KKz55761v0PTFmau1c0ICBXv7bqQ8SU/kVIEGncfqCobRrspNxbdzW5SZavJIMwLgWXE VF0Dk70D2BWZ5bfmA6Vtgjap1p/QC3KNq6PkQEofoP18Ak8XOhKBfNwqW3jq4RUjY2EX bCT9zwp8dwSUs8eEuPNGg+8LILyy3/O3th2BaSWtD2p+Y2KfHqKKL8oNG8/K3AQjUICw a2Dg== X-Forwarded-Encrypted: i=1; AFNElJ85ZqNDnQQPa7z1rpqFbaUkgVHuqIANSELGl2FR/1Jz/NRiJpjJ5f/IokOv0Xni2mj0buONQiIpBUP/dFI=@vger.kernel.org X-Gm-Message-State: AOJu0Yz7ndX9nwOjbnIodiAILZNMMwf2JvQYQUQIQj6SXDDH+ywtbSIN VLuObpBY1Sg55fNneQRVUPxdF3xm9CpBe92CG/oiYhoW8ZLkaRGjOcpgP+jbfLvV9Vg= X-Gm-Gg: Acq92OGVj+3H5tMyBLmPYjpLnyAkSqK2ba1B+xg+1WzHosSnLtNxI26uD1wHtRh1dnW 3QorL9Z6JnZocKfyA3YFeoQBfTwmlerah8v3KN7L4yQk9BzVhF8n9KBzljmEAPO+g4A/+b3Gfw4 /mAuHkCramWyuD/JS+ARtYGTVQKYbfS+8/r/aULiP090F9d8Iu5gbk4Ir2z1ibbDLdjESnDmZPl 00e3vh+qLxh/hwUIs+Uz8xDCSnDjrJYik7X5FwkCn5Z45cne5qME877KOjWJZbeL1s9Nr0xwQfH Gnj4hbfm8tnuFR5Je4Qunpj1y8+b9ARgV3DhcgpvlS2HbTpl2yqfWv89MxbZigHUZ1H+KfngCMv o6scDztr3h2SIWFPmNJnhWTtdm0swUBEEjJFC9AjxcFBJTXoZtNR7aIRTppt9ycDEBthxLWK4eL XaezOh1ls4C05HMnbp2PaE4D9xkdT1k6ku2qQno0KT4cGF2dzdVM7R7pVlweu10pMa063WwKOYj 5ssveeEW0eDYnJawUSqO2GNjYoFXCinuvSCzIy7naQ= X-Received: by 2002:a05:620a:7107:b0:8f8:d17c:9f9 with SMTP id af79cd13be357-9161c03e72amr1728829085a.16.1781471834790; Sun, 14 Jun 2026 14:17:14 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-91619f05fe7sm927730385a.12.2026.06.14.14.17.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 14 Jun 2026 14:17:14 -0700 (PDT) From: Michael Bommarito To: Melissa Wen , Maira Canal Cc: Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Iago Toral Quiroga , dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org Subject: [PATCH v2] drm/v3d: bound CPU-job query writes to their destination BO Date: Sun, 14 Jun 2026 17:16:44 -0400 Message-ID: <20260614211644.217116-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 7bit The V3D_SUBMIT_CPU query CPU jobs store a user-supplied destination offset (and, for the copy variants, a per-query stride and query count) on the job and consume them at exec time without checking that the writes stay inside the destination 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. None of these are bounded against the BO size, so a render-node user (DRM_RENDER_ALLOW, no master, no capability) can make the handlers write past the BO's vmap mapping. Validate the full write extent against the destination BO size once the BOs are looked up, before the job is queued, rejecting out-of-range or overflowing geometry with -EINVAL. The copy extent offset + (count - 1) * stride + write_size is accumulated with check_*_overflow() so a u32 product cannot wrap the bound, and the performance slot count is computed the same way since nperfmons and ncounters are user values. The bare timestamp writes and the copy source reads are a single fixed u64 slot per query and are bounded directly. Fixes: 9ba0ff3e083f ("drm/v3d: Create a CPU job extension for the timestamp query job") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito --- v2: - Extend the bound to the bare TIMESTAMP_QUERY and RESET_TIMESTAMP_QUERY job types, which also write one u64 per query into bo[0] at a user-controlled offset (Maira Canal). - Simplify v3d_cpu_job_bounds_check(): the timestamp source/dest u64 slots are bounded directly without the can-never-overflow check_mul_overflow(); only the genuinely user-sized copy extent and the performance slot count keep check_*_overflow(). - Drop the redundant local in v3d_check_copy_extent(); rename the gate to v3d_cpu_job_bounds_check(); reword the helper comments. - Drop the KUnit reproducer patch; the suite is kept out of tree. - Re-pin Fixes: to the earliest introducing commit (the timestamp query job) now that the bare timestamp writes are covered. Reproduced under KASAN with an out-of-tree KUnit driving the real handlers over shmem-backed BOs: with a query offset at the BO size the stock tree reports a vmalloc-out-of-bounds write in each of v3d_copy_query_results() (4 bytes), v3d_timestamp_query() (8 bytes) and v3d_reset_timestamp_queries() (8 bytes); with this patch the submit-time gate rejects the geometry and all in-bounds controls still pass. The write is plain CPU memory and not architecture specific (x86_64, COMPILE_TEST). drivers/gpu/drm/v3d/v3d_submit.c | 102 +++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index ee4512db294b3..fe11fd7e6e14a 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -1246,6 +1246,104 @@ 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, u32 write_size) +{ + u32 last; + + if (!count) + return 0; + + if (check_mul_overflow(stride, count - 1, &last) || + check_add_overflow(last, write_size, &last) || + check_add_overflow(last, offset, &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; + u32 slots, write_size; + int 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]); + + if (check_mul_overflow(job->performance_query.nperfmons, + (u32)DRM_V3D_MAX_PERF_COUNTERS, &slots)) + goto err_range; + + if (copy->availability_bit) { + u32 avail_slots; + + if (check_add_overflow(job->performance_query.ncounters, + 1u, &avail_slots)) + goto err_range; + slots = max(slots, avail_slots); + } + + if (check_mul_overflow(slots, elem, &write_size)) + goto err_range; + + return v3d_check_copy_extent(dev, dst->base.base.size, + copy->offset, copy->stride, + job->performance_query.count, + write_size); + 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 @@ -1317,6 +1415,10 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, if (ret) goto fail; + ret = v3d_cpu_job_bounds_check(cpu_job); + if (ret) + goto fail; + ret = v3d_lock_bo_reservations(&cpu_job->base, &acquire_ctx); if (ret) goto fail; -- 2.53.0