From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qv1-f41.google.com (mail-qv1-f41.google.com [209.85.219.41]) (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 829B63A3E95 for ; Sun, 14 Jun 2026 13:11:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.41 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781442671; cv=none; b=dnJLD//gMX8ZZyWXnk6EUu8a8fn1j5Ewb4Uh+nLttcB5xebMJuMWsDMy9A4W7z/4pZvlKN9Qg0LYYJVWB+qX8vIlHK8KcpPld2+gZ/SonxntIhJ3ttUdVxhWCulIMzMwRiaQiZ10BCS5URAMPfNsr7zYMQaFTByDMvX3Dl/dhDo= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781442671; c=relaxed/simple; bh=6ckLtPsJaG7puVhimM5VXaCe6J+jRLHmEDrB7DQFAfo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CFqPNE9iIQ5e51wWE0JTulrgf7LBNKBPU8Xo0gccV36pHtyfTYFjNpXblHqwkPKKx3mM0nKay9ay89KbPk0xgn0XO1fSyg8w+Rz8QEG9HkhgoZFLUMxqa7xq/LVBHxJWlqrU0RtK5ZxgLQ/Jcd87C0jGqEnZSlYP8+/V0Zu0dP4= 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=Hi77jq0q; arc=none smtp.client-ip=209.85.219.41 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="Hi77jq0q" Received: by mail-qv1-f41.google.com with SMTP id 6a1803df08f44-8ce9df31840so20329276d6.1 for ; Sun, 14 Jun 2026 06:11:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781442668; x=1782047468; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=OBor/vCL6YmtjmbcB0YXRqAJQFpdC1PJkqz6hn7PPv8=; b=Hi77jq0qbjDB/euFkytZeJPyebYN7nvxcmF3RXb4E03OpTqp8/QNHgLTP+hc4q0nd/ Lp6AAtCz6PG8SNVAqE7wxnWEGCq+GIqK0GiLSZQC5QcZBbplZRCJvJ1pa/6CakfBuZiL I4Wvbv3vVUPDuuxNbW5xk7wN8u1XX5Euym9E/w/LTdzwW7MktWXRYxxAf9ZW13p6Z74m xPqItBXYkFck2FGxdbTfNarTfH2P9bkfG+KUIhaWQtibtX4KLb3577DXr/5JGF1frtrA UZwH+8xb0q7Hx0Y9BmdT9MXL9Lsdvj5A2Zxtj1OSVh7qjvptbpYoxFMWdzBCH47e+GP1 wYPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781442668; x=1782047468; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=OBor/vCL6YmtjmbcB0YXRqAJQFpdC1PJkqz6hn7PPv8=; b=GfUJwjqr8zfp62U47ebR5dBy12Wx5iXfolNsIUMhv97AyiY9Dm5TKEJEouz110BO77 vjFbDZpb4CBvRDWZ52vywlqRD61cnQa5r0Tns3D6exoi76EyEcyJzk46GAGWr0IdjncC LJXm/IzOxJr2Taj4cpi34zvrKZ3GZjLykOiwVgvfcd4PywFRHxcxDkVXdluSZp09OM/G K7HWMnuRSwVCf0Og8kaxkRWlTlkdKtLuxkkYPITElv6ASGlAGurLB3okV2/F/L9YKEgO kTz5lDl+3eJu8mihdj/n1JbrzuLIFdWD7FoVyMwwKFf9ESLiV7wI5GilOyPZ4X0jR7Lf FYSg== X-Forwarded-Encrypted: i=1; AFNElJ9gJYCbeyXW+izjFTCu2DaRouhW9cymIE8t8jCzLlJCrOYxfXL5gQcy9Qlx6DBdCYiV5WjHDgB3X6Lp1UA=@vger.kernel.org X-Gm-Message-State: AOJu0YzxqceArp0qRehjerDae5Nne1hdsAU9hy2zVYIovBEwLiEElJVb 3VZi8eqLWH03hKRdcTo8XL/Hbd7ibJH3xdNOqe8hSiddVvhq7oZ2vROC X-Gm-Gg: Acq92OE98WYVTOfoW0PqaSIEzMWuzV/eTKCn8vAU4Ck7ETsrKYus/VDcof8srPXTB/C n2LJ11PYxmpcZBhpeKMbs2qFruKEw4yKsmfg2QDVDoHKduKr700KEFfxmh+lJDDRhuVP7yqGYwx RWY1Q7Skmc6W/VB7FfDmrCjGqdkFK3TQbv2VqzsIwltmGJBLE62sCC4HDkpQtmUOxEFaVJ2swah 8KUXFqzPUu7fmGe10FxZiXuVz1xwG5N6CgNusJETaWiKk1o3gkZQRuJRSN8ACvM0gTxbRbBUsfP xdyjLoTYPzQikeEKjxJcY9rUAYY6qMp1LSiVsiGlpjEo0/MA7AYgOGga/tSr2nqNWUObrRj9l95 dAivBwnHFkeQgIdA/eMNMEH/a3DuYgSedt9IfxSRGkp21gLdDxW4Yq5T3CvSGrGElNfAaS00YZf hyZLJ6sF4nX90PITHW+LXhisPcMYT7MjpmThRtr0lUm1YHeK7meYbqOvqKbZo0mU4EXOMljGNYJ IW3eAzw5orcqgpCzZVcCo92i+knlXjA6OVxlnDJYNs= X-Received: by 2002:a05:622a:5918:b0:517:9157:ecf3 with SMTP id d75a77b69052e-517fe5c95bdmr162885541cf.36.1781442668224; Sun, 14 Jun 2026 06:11:08 -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 d75a77b69052e-5196073d7fbsm25145811cf.30.2026.06.14.06.11.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 14 Jun 2026 06:11:06 -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 2/2] drm/v3d: add KUnit reproducer for the copy-query out-of-bounds write Date: Sun, 14 Jun 2026 09:11:00 -0400 Message-ID: <20260614131100.2525157-3-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260614131100.2525157-1-michael.bommarito@gmail.com> References: <20260614131100.2525157-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Add a KUnit suite (CONFIG_DRM_V3D_COPY_QUERY_KUNIT_TEST, builds under COMPILE_TEST) that drives the real v3d_copy_query_results() CPU-job handler over a shmem-backed BO and exercises the destination-buffer bounds the previous patch adds. Under KASAN the trigger case reproduces the vmalloc-out-of-bounds write on the stock tree and passes once the bounds patch is applied; two in-bounds controls pass on both trees. Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-8 --- The COPY_TIMESTAMP_QUERY path is hardware-free: with an unsignalled query the result read is skipped and only the availability bit is written, so the copy runs on CPU memory alone. The trigger uses a copy offset at the BO size, so the write lands past the one-page vmap mapping and KASAN (CONFIG_KASAN_VMALLOC) reports a vmalloc-out-of-bounds write; the two in-bounds controls prove the synthesised setup is not itself the cause. With patch 1 applied the trigger is rejected by the submit-time gate (exposed for the test) and the copy never runs. x86_64, COMPILE_TEST; the write is plain CPU memory and not architecture specific. drivers/gpu/drm/v3d/Kconfig | 10 ++ drivers/gpu/drm/v3d/v3d_copy_query_kunit.c | 172 +++++++++++++++++++++ drivers/gpu/drm/v3d/v3d_drv.h | 3 + drivers/gpu/drm/v3d/v3d_sched.c | 4 + drivers/gpu/drm/v3d/v3d_submit.c | 2 +- 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/v3d/v3d_copy_query_kunit.c diff --git a/drivers/gpu/drm/v3d/Kconfig b/drivers/gpu/drm/v3d/Kconfig index ce62c5908e1db..f293eb95a73c8 100644 --- a/drivers/gpu/drm/v3d/Kconfig +++ b/drivers/gpu/drm/v3d/Kconfig @@ -11,3 +11,13 @@ config DRM_V3D Choose this option if you have a system that has a Broadcom V3D 3.x or newer GPUs. SoCs supported include the BCM2711, BCM7268 and BCM7278. + +config DRM_V3D_COPY_QUERY_KUNIT_TEST + bool "KUnit test for V3D CPU-job copy-query bounds" + depends on DRM_V3D && KUNIT=y + select DRM_KUNIT_TEST_HELPERS + help + Builds a KUnit suite that drives the V3D CPU-job copy-timestamp-query + handler over a real shmem-backed BO to exercise the destination-buffer + bounds. Used to reproduce the copy-query out-of-bounds write under + KASAN. If in doubt, say N. diff --git a/drivers/gpu/drm/v3d/v3d_copy_query_kunit.c b/drivers/gpu/drm/v3d/v3d_copy_query_kunit.c new file mode 100644 index 0000000000000..4296449de7230 --- /dev/null +++ b/drivers/gpu/drm/v3d/v3d_copy_query_kunit.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * KUnit coverage for the V3D CPU-job copy-query OOB write. Drives the real + * v3d_copy_query_results() (COPY_TIMESTAMP_QUERY) over a shmem-backed v3d_bo: + * a copy->offset at the BO size makes the availability write land past the + * one-page vmap, which KASAN reports. A NULL fence skips the result read, so + * no V3D engine is needed (Level 2). Two in-bounds controls pass on both. + * Included from v3d_sched.c. + */ +#include + +#include + +#include +#include +#include +#include +#include + +/* One page per BO; the OOB write lands just past this vmap mapping. */ +#define V3D_COPY_TEST_BO_SIZE PAGE_SIZE + +struct v3d_copy_test_ctx { + struct drm_device *drm; + struct v3d_bo *dst; /* to_v3d_bo(job->base.bo[0]) */ + struct v3d_bo *ts; /* to_v3d_bo(job->base.bo[1]) */ + struct drm_gem_object *bo_array[2]; + struct drm_syncobj syncobj[4]; /* fence == NULL -> unavailable */ + struct v3d_timestamp_query queries[4]; + struct v3d_cpu_job job; +}; + +/* Standard shmem teardown; we have no v3d_dev for v3d_free_object(). */ +static void v3d_copy_test_free_bo(void *p) +{ + struct v3d_bo *bo = p; + + if (bo->vaddr) + v3d_put_bo_vaddr(bo); + if (refcount_read(&bo->base.pages_pin_count)) + drm_gem_shmem_unpin(&bo->base); + drm_gem_shmem_free(&bo->base); +} + +static struct v3d_bo * +v3d_copy_test_make_bo(struct kunit *test, struct drm_device *drm) +{ + struct drm_gem_shmem_object *shmem; + struct drm_gem_object *obj; + struct v3d_bo *bo; + int ret; + + /* v3d_create_object sizes the alloc for struct v3d_bo (bo->vaddr). */ + shmem = drm_gem_shmem_create(drm, V3D_COPY_TEST_BO_SIZE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem); + + obj = &shmem->base; + bo = to_v3d_bo(obj); + + /* Real page backing for v3d_get_bo_vaddr()'s vmap(). */ + ret = drm_gem_shmem_pin(shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, v3d_copy_test_free_bo, bo); + KUNIT_ASSERT_EQ(test, ret, 0); + + return bo; +} + +/* Build a COPY_TIMESTAMP_QUERY job and run the real copy handler. */ +static void v3d_copy_test_run(struct kunit *test, u32 offset, u32 stride, + u32 count) +{ + struct v3d_copy_test_ctx *c; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_GEM); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + /* Route BO creation through the driver hook (devm drm_driver is writable). */ + ((struct drm_driver *)drm->driver)->gem_create_object = v3d_create_object; + + c = kunit_kzalloc(test, sizeof(*c), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c); + c->drm = drm; + + c->dst = v3d_copy_test_make_bo(test, drm); + c->ts = v3d_copy_test_make_bo(test, drm); + + c->bo_array[0] = &c->dst->base.base; + c->bo_array[1] = &c->ts->base.base; + + /* fence == NULL: result read skipped; only the availability bit is written. */ + KUNIT_ASSERT_LE(test, count, (u32)ARRAY_SIZE(c->queries)); + for (u32 i = 0; i < count; i++) { + c->syncobj[i].fence = NULL; + c->queries[i].offset = 0; /* in bounds for the source BO */ + c->queries[i].syncobj = &c->syncobj[i]; + } + + c->job.job_type = V3D_CPU_JOB_TYPE_COPY_TIMESTAMP_QUERY; + c->job.base.bo = c->bo_array; + c->job.base.bo_count = 2; + c->job.timestamp_query.queries = c->queries; + c->job.timestamp_query.count = count; + + /* drm_device is the first member of v3d_dev; the cast resolves &v3d->drm back. */ + c->job.base.v3d = (struct v3d_dev *)c->drm; + + c->job.copy.do_64bit = false; /* 4-byte writes */ + c->job.copy.do_partial = false; + c->job.copy.availability_bit = true; /* drives the index-1 write */ + c->job.copy.offset = offset; + c->job.copy.stride = stride; + +#if defined(V3D_CPU_JOB_COPY_BOUNDS_CHECKED) + /* Patched tree: run the same submit-time gate; out-of-range is rejected before the copy. */ + if (v3d_cpu_job_check_copy_bounds(&c->job)) { + KUNIT_EXPECT_GT_MSG(test, c->job.copy.offset + count * stride, + (u32)V3D_COPY_TEST_BO_SIZE, + "bounds gate rejected an in-bounds geometry"); + return; + } +#endif + + v3d_copy_query_results(&c->job); +} + +/* Control: one query at offset 0; availability bit at byte 4, in bounds. */ +static void v3d_copy_query_control_inbounds(struct kunit *test) +{ + v3d_copy_test_run(test, 0, 8, 1); +} + +/* Control: two queries, stride 8; second availability bit at byte 12, in bounds. */ +static void v3d_copy_query_control_inbounds_multi(struct kunit *test) +{ + v3d_copy_test_run(test, 0, 8, 2); +} + +/* Trigger: copy->offset == BO size; write past the vmap mapping (stock OOB, patched rejected). */ +static void v3d_copy_query_trigger_oob(struct kunit *test) +{ + v3d_copy_test_run(test, V3D_COPY_TEST_BO_SIZE, 8, 1); +} + +static struct kunit_case v3d_copy_query_cases[] = { + KUNIT_CASE(v3d_copy_query_control_inbounds), + KUNIT_CASE(v3d_copy_query_control_inbounds_multi), + KUNIT_CASE(v3d_copy_query_trigger_oob), + {} +}; + +static struct kunit_suite v3d_copy_query_suite = { + .name = "v3d_copy_query", + .test_cases = v3d_copy_query_cases, +}; + +kunit_test_suite(v3d_copy_query_suite); + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 6a3cad9334398..a4b772385070f 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -583,6 +583,9 @@ int v3d_submit_csd_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +/* Exposed for the copy-query KUnit; its presence marks the bounds patch applied. */ +#define V3D_CPU_JOB_COPY_BOUNDS_CHECKED 1 +int v3d_cpu_job_check_copy_bounds(struct v3d_cpu_job *job); /* v3d_irq.c */ int v3d_irq_init(struct v3d_dev *v3d); diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c index 1855ef5b3b5fe..7884863376702 100644 --- a/drivers/gpu/drm/v3d/v3d_sched.c +++ b/drivers/gpu/drm/v3d/v3d_sched.c @@ -901,3 +901,7 @@ v3d_sched_fini(struct v3d_dev *v3d) drm_sched_fini(&v3d->queue[q].sched); } } + +#if IS_ENABLED(CONFIG_DRM_V3D_COPY_QUERY_KUNIT_TEST) +#include "v3d_copy_query_kunit.c" +#endif diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index 23e19dacfdce2..ec0b94ada73ff 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -1268,7 +1268,7 @@ v3d_check_copy_extent(struct drm_device *dev, size_t bo_size, } /* Bound the copy-query CPU-job writes; the exec-time copy does not. */ -static int +int v3d_cpu_job_check_copy_bounds(struct v3d_cpu_job *job) { struct drm_device *dev = &job->base.v3d->drm; -- 2.53.0