From: sashiko-bot@kernel.org
To: "Lukas Zapolskas" <lukas.zapolskas@arm.com>
Cc: dri-devel@lists.freedesktop.org
Subject: Re: [PATCH v7 6/8] drm/panthor: Implement sample handling
Date: Sun, 11 Oct 2026 17:56:44 +0000 [thread overview]
Message-ID: <sashiko-outbox-167363@kernel.org> (raw)
In-Reply-To: <20261011-panthor-performance-counters-v7-6-12c32495062f@arm.com>
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] drm: Integer overflow in user_sample_size bypasses BO size validation
- [High] drm: 64-bit division by variable size_t breaks 32-bit builds
- [High] workqueue: CPU hog due to immediate requeueing in panthor_perf_sample_worker during reset
- [High] drm: Unbounded loop in panthor_perf_handle_fw_samples allows firmware to hang kernel
- [High] drm: Incompatible pointer type in bitmap_from_arr64 corrupts memory on 32-bit big-endian
- [High] drm: panthor_perf_pool_destroy leaks session pool and PM references on unplug
- [High] drm: panthor_perf_session_destroy fails to re-enable global FW sampling
- [High] drm: Missing memory barriers in FW ringbuffer communication
- [High] drm: sleeping lock in wait_event_timeout condition triggers might_sleep() warning
- [High] drm/panthor: UAF and NULL-deref when device unplug races with file close
- [Medium] drm: Timestamp accumulation zeroed out by skipped blocks
- [Medium] drm: Undefined behavior in BLK_MASK macro when argument is zero
- [Medium] drm/panthor: memory and pm runtime leak in panthor_perf_pool_destroy()
--
commit 3f117df9269c8346ebf5d90d19e892acc18e5210
Author: Adrián Larumbe <adrian.larumbe@collabora.com>
drm/panthor: Implement sample handling
Adds performance counter sample handling for the Panthor driver. It
introduces a ring buffer for firmware-to-kernel sample communication,
handles interrupts for new samples or overflows, and accumulates partial
hardware samples into a user-facing buffer.
> diff --git a/drivers/gpu/drm/panthor/panthor_perf.c b/drivers/gpu/drm/panthor/panthor_perf.c
> index 76c3469811508..c66ad658a95db 100644
> --- a/drivers/gpu/drm/panthor/panthor_perf.c
> +++ b/drivers/gpu/drm/panthor/panthor_perf.c
[ ... ]
> static void panthor_perf_handle_fw_samples(struct panthor_device *ptdev,
> u32 extract_idx, u32 insert_idx)
> {
> struct panthor_perf *perf = ptdev->perf;
> const size_t ann_block_size =
> get_annotated_block_size(ptdev->perf_info.counters_per_block);
> u32 i;
>
> for (i = extract_idx; i != insert_idx; i++) {
[Severity: High]
Are there any bounds checks on extract_idx and insert_idx? Since these
come directly from untrusted firmware shared memory, could a malicious or
buggy firmware return a bogus insert_idx (e.g., extract_idx + U32_MAX)
that forces this loop to execute billions of times, permanently stalling the
kernel worker thread?
[ ... ]
> +#define BLK_MASK(x) GENMASK_ULL((x) - 1, 0)
> +
> +static u64 get_sc_mask(struct panthor_device *ptdev)
> +{
[ ... ]
> +static int panthor_perf_setup_fw_buffer(struct panthor_device *ptdev, struct panthor_perf *perf)
> +{
[ ... ]
> + const struct panthor_perf_fw_block blocks[DRM_PANTHOR_PERF_BLOCK_MAX] = {
> + [DRM_PANTHOR_PERF_BLOCK_METADATA] = {
> + .block_count = has_metadata ? 1 : 0,
> + .phys_mask = has_metadata ? BIT_ULL(0) : 0,
> + },
> + [DRM_PANTHOR_PERF_BLOCK_FW] = {
> + .block_count = info->fw_blocks,
> + .phys_mask = info->fw_blocks ? BLK_MASK(info->fw_blocks) : 0,
> + },
> + [DRM_PANTHOR_PERF_BLOCK_CSHW] = {
> + .block_count = info->cshw_blocks,
> + .phys_mask = BLK_MASK(info->cshw_blocks),
> + },
[Severity: Medium]
If any hardware capabilities (like info->cshw_blocks, tiler_blocks, or
memsys_blocks) have a count of 0, does BLK_MASK(0) expand to
GENMASK_ULL(0 - 1, 0)? Passing -1 (or ULLONG_MAX) as the high bit
argument to GENMASK_ULL triggers an invalid shift and undefined behavior.
Should these usages have a ternary guard for zero similar to how
info->fw_blocks is protected?
[ ... ]
> +static void session_accumulate_sample(struct panthor_device *ptdev,
> + struct panthor_perf_session *session,
> + u8 *session_sample, u8 *perf_sample)
> +{
> + const struct drm_panthor_perf_info *const perf_info = &ptdev->perf_info;
> + struct panthor_perf_enable_masks *em = session->enabled_counters;
> + struct panthor_perf_sample_block_iter iter;
> + const u32 counters = perf_info->counters_per_block;
> +
> + sample_block_iter_init(perf_info, perf_sample, session_sample, &iter);
> +
> + while (sample_block_iter_next(&iter)) {
> + DECLARE_BITMAP(enabled_ctrs, PANTHOR_PERF_EM_BITS);
> + const struct panthor_perf_counter_block *src_blk = iter.src_blk;
> + struct panthor_perf_counter_block *dst_blk = iter.dst_blk;
> +
> + bitmap_from_arr64(enabled_ctrs, em->mask[dst_blk->header.block_type],
> + PANTHOR_PERF_EM_BITS);
[Severity: High]
bitmap_from_arr64() expects a const u64 * source, but em->mask[type]
is declared as an unsigned long array. On 32-bit big-endian architectures,
reading a 64-bit integer from a pair of 32-bit unsigned long values
effectively swaps the two words. Will this silently scramble the entire
enable mask?
> + bitmap_clear(enabled_ctrs, 0, PANTHOR_HEADER_COUNTERS);
> +
> + dst_blk->counters[PANTHOR_CTR_TIMESTAMP_HI] =
> + src_blk->counters[PANTHOR_CTR_TIMESTAMP_HI];
> + dst_blk->counters[PANTHOR_CTR_TIMESTAMP_LO] =
> + src_blk->counters[PANTHOR_CTR_TIMESTAMP_LO];
[Severity: Medium]
Because the intermediate buffer perf->sample is completely zeroed out at
the end of each worker run, if the firmware did not produce a sample for a
given block in the current batch (prfcnt_en == 0), its block remains all
zeros.
Since this unconditionally copies timestamps from the intermediate block,
will this overwrite the session's previously accumulated valid timestamp
with zero?
[ ... ]
> +static void session_copy_sample(struct panthor_device *ptdev,
> + struct panthor_perf_session *session, u32 flags)
> +{
[ ... ]
> + new_sample = session->samples + (insert_idx % session->ringbuf_slots) * sample_size;
[Severity: High]
Because insert_idx is a u64 and session->ringbuf_slots is a size_t,
does this perform a 64-bit by 32-bit modulo operation on 32-bit
architectures? This can result in a link-time error (__umoddi3 undefined).
Since ringbuf_slots is guaranteed to be a power of two during creation,
could this be optimized to a bitwise AND
(insert_idx & (session->ringbuf_slots - 1)) to avoid the division entirely?
[ ... ]
> +static void panthor_perf_sample_worker(struct work_struct *work)
> +{
> + struct panthor_perf *perf = container_of(work, struct panthor_perf, sample_work);
> + struct panthor_device *const ptdev = perf->ptdev;
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + const u32 extract_idx = READ_ONCE(glb_iface->input->perfcnt_extract);
> + const u32 insert_idx = READ_ONCE(glb_iface->output->perfcnt_insert);
> + const u32 ack = READ_ONCE(glb_iface->output->ack);
> + const u32 req = READ_ONCE(glb_iface->input->req);
> + const u32 events = (req ^ ack) & (GLB_PERFCNT_THRESHOLD | GLB_PERFCNT_OVERFLOW);
> + const u32 flags = events & GLB_PERFCNT_OVERFLOW ? DRM_PANTHOR_PERF_SAMPLE_OVERFLOW : 0;
> +
> + /* Defer sample processing until the pending reset completes. */
> + if (panthor_device_reset_is_pending(ptdev)) {
> + queue_work(perf->sample_workq, &perf->sample_work);
> + return;
> + }
[Severity: High]
Since sample_workq is created with WQ_HIGHPRI | WQ_UNBOUND, does
immediately re-queueing the work create a tight busy-loop? Could this pin a
CPU at 100% utilization until the reset completes, potentially starving the
reset handler itself? Would it be safer to use queue_delayed_work() or
rely on the reset handler to kick the queue?
> +
> + /* A stale notification must not complete a userspace sample request. */
> + if (extract_idx == insert_idx)
> + goto ack_events;
> +
> + panthor_perf_handle_fw_samples(ptdev, extract_idx, insert_idx);
> + WRITE_ONCE(glb_iface->input->perfcnt_extract, insert_idx);
[Severity: High]
Is an smp_rmb() needed before parsing the payload in
panthor_perf_handle_fw_samples()? The architecture's weakly-ordered memory
model might permit the CPU to speculatively load sample data before the
insert_idx read guarantees the data is fully written.
Similarly, is an smp_wmb() required before the WRITE_ONCE updates
perfcnt_extract, to ensure all payload reads complete before the firmware
reclaims the slots?
[ ... ]
> +static int panthor_perf_request_sync(struct panthor_perf *perf,
> + struct panthor_perf_session *session, session_state_cb cb)
> +{
> + int ret;
> +
> + perf_fw_request_sample(perf);
> +
> + ret = wait_event_timeout(session->state_wq, cb(session),
> + msecs_to_jiffies(1000));
[Severity: High]
wait_event_timeout() explicitly sets the task state to
TASK_UNINTERRUPTIBLE before evaluating its condition. The condition
callbacks (like session_is_stopped_locked and
session_is_active_locked) unconditionally acquire a sleeping lock
(mutex) via guard(mutex).
Does calling a sleeping lock while the task state is not TASK_RUNNING
violate kernel scheduling rules and trigger might_sleep() kernel warnings
every time the condition is checked?
Furthermore, if the mutex is contended, the slowpath overwrites the task
state back to TASK_RUNNING. When the condition callback returns false,
schedule_timeout() is called with TASK_RUNNING, causing it to return
immediately without sleeping and create a busy-wait loop.
[ ... ]
> +int panthor_perf_session_create(struct drm_file *file,
> + struct drm_panthor_perf_session_create *create_args,
> + unsigned long *sid)
> +{
[ ... ]
> + user_sample_size = ptdev->perf_info.sample_size * slots;
> +
> + ret = panthor_perf_bo_get(file, create_args->ringbuf_handle,
> + user_sample_size, &ringbuffer);
[Severity: Critical]
Userspace provides create_args->sample_slots, which is validated only by
is_power_of_2(). Could slots be large enough that multiplying it by
ptdev->perf_info.sample_size overflows the 32-bit size_t
user_sample_size on 32-bit platforms, wrapping it to 0?
If it wraps to 0, panthor_perf_bo_get() bypasses size validation entirely
because of the "if (expected_size && ...)" check. Does this bind a massive
slots count to a maliciously small GEM BO, allowing out-of-bounds memory
corruption when the worker emits data?
[ ... ]
> +int panthor_perf_session_destroy(struct panthor_file *pfile, u32 handle)
> +{
[ ... ]
> + xa_erase(&pfile->sessions->xa, handle);
> + ret = perf_disable(perf);
> +
> + scoped_guard(mutex, &perf->session_lock)
> + perf_detach_session(perf, session);
> + }
>
> session_put(session);
[Severity: High]
After stopping firmware sampling via perf_disable(perf) and detaching the
session, this function neglects to call perf_enable(perf). If a single
session is destroyed by userspace, does this permanently stop firmware
sampling globally and starve all remaining active sessions of data?
[ ... ]
> void panthor_perf_unplug(struct panthor_device *ptdev)
> {
> + int ret;
> struct panthor_perf *perf = ptdev->perf;
>
> if (!perf)
> return;
>
> + scoped_guard(mutex, &perf->op_lock)
> + ret = perf_disable(perf);
> + if (ret)
> + drm_err(&perf->ptdev->base, "Failed to disable performance counter sampling!");
> +
> + disable_work_sync(&perf->sample_work);
> +
> + panthor_kernel_bo_vunmap(perf->rb);
> + panthor_kernel_bo_destroy(perf->rb);
> +
> ptdev->perf = NULL;
> }
[Severity: High]
If panthor_device_unplug() runs concurrently with the closing of a DRM
file descriptor containing multiple sessions, could panthor_postclose()
safely bypass ioctl synchronization?
If panthor_perf_unplug() frees perf->rb and clears ptdev->perf to
NULL while panthor_perf_pool_destroy() is iterating, could xa_for_each()
either pass a NULL pointer to panthor_perf_session_remove() (causing a
panic) or call perf_enable() on the freed perf->rb, triggering a
use-after-free when writing its virtual address to the device register?
[ ... ]
> void panthor_perf_pool_destroy(struct panthor_file *pfile)
> {
> struct panthor_perf_pool *pool = pfile->sessions;
> unsigned long handle;
> struct panthor_perf_session *session;
>
> if (IS_ERR_OR_NULL(pool))
> return;
>
> + if (!pfile->ptdev->perf)
> + return;
> +
> xa_for_each(&pool->xa, handle, session) {
> xa_erase(&pool->xa, handle);
> + panthor_perf_session_remove(pfile->ptdev->perf, session);
> session_put(session);
> + pm_runtime_put_sync(pfile->ptdev->base.dev);
> }
>
> xa_destroy(&pool->xa);
[Severity: Medium]
If panthor_device_unplug() has already cleared ptdev->perf to NULL,
this early return completely bypasses the xa_for_each() cleanup loop.
Does this result in a persistent memory leak of the xarray, all active
panthor_perf_session objects, the pool structure itself, and the
associated Power Management (PM) runtime references taken during session
creation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261011-panthor-performance-counters-v7-0-12c32495062f@arm.com?part=6
next prev parent reply other threads:[~2026-10-11 17:56 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-11 17:36 [PATCH v7 0/8] drm/panthor: Performance counter implementation with single manual client support Lukas Zapolskas
2026-10-11 17:36 ` [PATCH v7 1/8] drm/panthor: Add performance counter uAPI Lukas Zapolskas
2026-10-11 17:47 ` sashiko-bot
2026-10-11 17:36 ` [PATCH v7 2/8] drm/panthor: Add register definitions for perf Lukas Zapolskas
2026-10-11 17:36 ` [PATCH v7 3/8] drm/panthor: Add DEV_QUERY.PERF_INFO handling Lukas Zapolskas
2026-10-11 17:36 ` [PATCH v7 4/8] drm/panthor: Add panthor perf initialization and termination Lukas Zapolskas
2026-10-11 17:36 ` [PATCH v7 5/8] drm/panthor: Introduce sampling sessions to handle userspace clients Lukas Zapolskas
2026-10-11 17:51 ` sashiko-bot
2026-10-11 17:36 ` [PATCH v7 6/8] drm/panthor: Implement sample handling Lukas Zapolskas
2026-10-11 17:56 ` sashiko-bot [this message]
2026-10-13 4:52 ` kernel test robot
2026-10-11 17:36 ` [PATCH v7 7/8] drm/panthor: Add suspend, resume and reset handling Lukas Zapolskas
2026-10-11 17:53 ` sashiko-bot
2026-10-11 17:36 ` [PATCH v7 8/8] drm/panthor: Expose the panthor perf ioctls Lukas Zapolskas
2026-10-11 17:52 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-167363@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=lukas.zapolskas@arm.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.