All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Philipp Stanner" <phasta@kernel.org>
Cc: dri-devel@lists.freedesktop.org, intel-gfx@lists.freedesktop.org,
	linux-trace-kernel@vger.kernel.org, media-ci@linuxtv.org,
	ojeda@kernel.org, gary@garyguo.net
Subject: Re: [RFC PATCH v2 3/3] dma-fence: Support unload for ops->get_XXX_name()
Date: Tue, 20 Oct 2026 10:27:18 +0000	[thread overview]
Message-ID: <sashiko-outbox-178830@kernel.org> (raw)
In-Reply-To: <20261020100034.759183-5-phasta@kernel.org>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] dma-fence: use-after-free on extern_lock in dma_fence_driver_name()
- [High] dma-fence: uninitialized locals in dma_fence_describe() when signaled
- [High] dma-fence: recursive spinlock deadlock via trace_dma_fence_destroy()
- [High] drm/i915: rq->lock ABBA deadlock in i915_request_show()
- [Low] dma-fence: stack VLAs in dma_fence and i915 fence name callers
- [Low] dma-fence: leftover rcu_read_lock() and outdated kernel-doc in name helpers
--

commit 9ca3a4e9e4ca7aa50b6bddada4c64fc3ed8e2861
Author: Philipp Stanner <phasta@kernel.org>

dma-fence: Support unload for ops->get_XXX_name()

This commit replaces the RCU-protected return values of
dma_fence_driver_name() and dma_fence_timeline_name() with callers
passing destination buffers that are populated under the fence lock.

> diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
> index e4350bce9cf9b..d473d4ea601a7 100644
> --- a/drivers/dma-buf/dma-fence.c
> +++ b/drivers/dma-buf/dma-fence.c
> @@ -555,23 +555,22 @@ void dma_fence_release(struct kref *kref)
>  	struct dma_fence *fence =
>  		container_of(kref, struct dma_fence, refcount);
>  	const struct dma_fence_ops *ops = fence->ops;
> +	const unsigned short len = 64;
> +	char timeline[len];
> +	char driver[len];

[Severity: Low]
In C11/gnu11, a const-qualified variable is not an integer constant
expression, so does declaring timeline[len] and driver[len] create stack
VLAs that trigger compiler warnings (and build failures with CONFIG_WERROR
or CONFIG_DRM_I915_WERROR) under -Wvla-larger-than=1 in
scripts/Makefile.warn?

The same pattern appears in dma_fence_describe(), intel_gt_watchdog_work(),
i915_request_show(), and timer_i915_sw_fence_wake(). Also, in
dma_fence_release(), these buffers were moved out of the unlikely error
block to the top of the function.

>  
> -	rcu_read_lock();
>  	trace_dma_fence_destroy(fence);

[ ... ]

> @@ -1017,23 +1015,20 @@ EXPORT_SYMBOL(dma_fence_set_deadline);
>   */
>  void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)
>  {
> -	const char __rcu *timeline = (const char __rcu *)"";
> -	const char __rcu *driver = (const char __rcu *)"";
> -	const char *signaled = "";
> -
> -	rcu_read_lock();
> +	const unsigned short len = 64;
> +	char timeline[len];
> +	char driver[len];
> +	char *signaled;
>  
>  	if (!dma_fence_is_signaled(fence)) {
> -		timeline = dma_fence_timeline_name(fence);
> -		driver = dma_fence_driver_name(fence);
> +		dma_fence_timeline_name(fence, timeline, len);
> +		dma_fence_driver_name(fence, driver, len);
>  		signaled = "un";
>  	}
>  
>  	seq_printf(seq, "%llu:%llu %s %s %ssignalled\n",
>  		   fence->context, fence->seqno, timeline, driver,
>  		   signaled);
> -
> -	rcu_read_unlock();
>  }
>  EXPORT_SYMBOL(dma_fence_describe);

[Severity: High]
What happens in dma_fence_describe() when dma_fence_is_signaled(fence)
returns true?

Previously, timeline, driver, and signaled were initialized to "" at
declaration. Now they are only assigned inside the
if (!dma_fence_is_signaled(fence)) block. When the fence is already
signaled, won't seq_printf() dereference the uninitialized stack pointer
signaled and read the uninitialized stack arrays timeline and driver?

Also, if the fence signals concurrently right after
!dma_fence_is_signaled(fence) evaluates to true,
dma_fence_timeline_name() and dma_fence_driver_name() will populate
"signaled-timeline" and "detached-driver" while signaled is set to "un".

> @@ -1129,20 +1124,22 @@ EXPORT_SYMBOL(dma_fence_init64);
>   * memory potentially accessed by the &dma_fence.ops functions, is forbidden
>   * after the fence has been signalled. Drivers are allowed to free that data,
>   * and some do.
> - *
> - * To allow safe access drivers are mandated to guarantee a RCU grace period
> - * between signalling the fence and freeing said data.
> - *
> - * As such access to the driver name is only valid inside a RCU locked section.
> - * The pointer MUST be both queried and USED ONLY WITHIN a SINGLE block guarded
> - * by the &rcu_read_lock and &rcu_read_unlock pair.
>   */
> -const char __rcu *dma_fence_driver_name(struct dma_fence *fence)
> +void dma_fence_driver_name(struct dma_fence *fence, char *s, unsigned short len)
>  {
> +	unsigned long flags;
> +	const char *tmp;
> +
> +	dma_fence_lock_irqsave(fence, flags);
> +
>  	if (!dma_fence_test_signaled_flag(fence))
> -		return (const char __rcu *)fence->ops->get_driver_name(fence);
> +		tmp = fence->ops->get_driver_name(fence);
>  	else
> -		return (const char __rcu *)"detached-driver";
> +		tmp = "detached-driver";
> +
> +	strscpy(s, tmp, len);
> +
> +	dma_fence_unlock_irqrestore(fence, flags);
>  }
>  EXPORT_SYMBOL(dma_fence_driver_name);
>  
> @@ -1158,19 +1155,21 @@ EXPORT_SYMBOL(dma_fence_driver_name);
>   * memory potentially accessed by the &dma_fence.ops functions, is forbidden
>   * after the fence has been signalled. Drivers are allowed to free that data,
>   * and some do.
> - *
> - * To allow safe access drivers are mandated to guarantee a RCU grace period
> - * between signalling the fence and freeing said data.
> - *
> - * As such access to the driver name is only valid inside a RCU locked section.
> - * The pointer MUST be both queried and USED ONLY WITHIN a SINGLE block guarded
> - * by the &rcu_read_lock and &rcu_read_unlock pair.
>   */
> -const char __rcu *dma_fence_timeline_name(struct dma_fence *fence)
> +void dma_fence_timeline_name(struct dma_fence *fence, char *s, unsigned short len)
>  {
> +	unsigned long flags;
> +	const char *tmp;
> +
> +	dma_fence_lock_irqsave(fence, flags);
> +
>  	if (!dma_fence_test_signaled_flag(fence))
> -		return (const char __rcu *)fence->ops->get_timeline_name(fence);
> +		tmp = fence->ops->get_timeline_name(fence);
>  	else
> -		return (const char __rcu *)"signaled-timeline";
> +		tmp = "signaled-timeline";
> +
> +	strscpy(s, tmp, len);
> +
> +	dma_fence_unlock_irqrestore(fence, flags);
>  }
>  EXPORT_SYMBOL(dma_fence_timeline_name);

[Severity: Critical]
Is it safe for dma_fence_driver_name() and dma_fence_timeline_name() to
unconditionally acquire dma_fence_lock_irqsave(fence, flags) before
checking if the fence is already signaled?

As noted in the kernel-doc comment above these functions, external memory
not embedded in struct dma_fence (including fence->extern_lock) must not
be accessed after the fence has signaled because drivers are allowed to
free the structure containing the lock once the fence is signaled:

  drivers/gpu/drm/panthor/panthor_sched.c:group_free_queue()
    dma_fence_put(queue->fence_ctx.last_fence);
    kfree(queue); /* frees queue->fence_ctx.lock */

  drivers/gpu/drm/msm/msm_fence.c:msm_fence_context_free()
    kfree(fctx); /* frees fctx->spinlock */

  drivers/gpu/drm/xe/xe_hw_fence.c:xe_hw_fence_irq_finish()
    synchronize_rcu(); /* before freeing irq containing irq->lock */

If a signaled fence is still held (for example in a sync_file) after the
driver frees the structure containing fence->extern_lock, and userspace
calls SYNC_IOC_FILE_INFO -> sync_fill_fence_info() ->
dma_fence_timeline_name(), won't dma_fence_lock_irqsave() dereference and
modify the freed spinlock?

[Severity: High]
Can acquiring dma_fence_lock_irqsave() inside dma_fence_driver_name() and
dma_fence_timeline_name() also cause a recursive spinlock deadlock when
trace_dma_fence_destroy is enabled?

In dma_fence_release(), trace_dma_fence_destroy(fence) is invoked when the
refcount drops to zero, which calls dma_fence_driver_name() and
dma_fence_timeline_name() in TP_fast_assign():

  dma_fence_put(fence)
    -> dma_fence_release()
      -> trace_dma_fence_destroy(fence)
        -> dma_fence_driver_name()
          -> dma_fence_lock_irqsave(fence, flags)

However, several paths in the kernel call dma_fence_put() while already
holding the fence's spinlock:

1. dma_fence_signal_timestamp_locked() holds dma_fence_spinlock(fence)
   while invoking callbacks such as dma_fence_chain_cb(), which calls
   dma_fence_put(f).

2. Drivers with shared external fence locks call dma_fence_put() while
   holding that lock, such as virtio_gpu_fence_event_process() (holding
   drv->lock), nouveau_fence_signal() (holding fctx->lock), and
   hw_fence_irq_run_cb() (holding irq->lock).

If the last reference is dropped on any of these paths while the
dma_fence_destroy tracepoint is active, won't dma_fence_lock_irqsave()
deadlock trying to acquire the already-held lock on the same CPU?

[Severity: Low]
Also, should the kernel-doc comments for dma_fence_driver_name() and
dma_fence_timeline_name() in drivers/dma-buf/dma-fence.c be updated to
document the new @s and @len parameters and void return, as well as the
get_driver_name and get_timeline_name comments in struct dma_fence_ops in
include/linux/dma-fence.h (which still state that the returned string is
RCU protected) and the leftover rcu_read_lock() wrappers around
trace_dma_fence_wait_start() and trace_dma_fence_wait_end() in
dma_fence_wait_timeout()?

> diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c
> index 2166bbdf7e4a4..0f08713b3b5e2 100644
> --- a/drivers/dma-buf/sync_file.c
> +++ b/drivers/dma-buf/sync_file.c

[ ... ]

> @@ -268,19 +269,11 @@ static long sync_file_ioctl_merge(struct sync_file *sync_file,
>  static int sync_fill_fence_info(struct dma_fence *fence,
>  				 struct sync_fence_info *info)
>  {
> -	const char __rcu *timeline;
> -	const char __rcu *driver;
> +	dma_fence_timeline_name(fence, info->obj_name, sizeof(info->obj_name));
> +	dma_fence_driver_name(fence, info->driver_name, sizeof(info->driver_name));
>  
>  	rcu_read_lock();
>  
> -	driver = dma_fence_driver_name(fence);
> -	timeline = dma_fence_timeline_name(fence);
> -
> -	strscpy(info->obj_name, rcu_dereference(timeline),
> -		sizeof(info->obj_name));
> -	strscpy(info->driver_name, rcu_dereference(driver),
> -		sizeof(info->driver_name));
> -
>  	info->status = dma_fence_get_status(fence);
>  	info->timestamp_ns =
>  		dma_fence_is_signaled(fence) ?

[Severity: Low]
In sync_fill_fence_info(), now that dma_fence_timeline_name() and
dma_fence_driver_name() have been moved outside the RCU critical section,
is the remaining rcu_read_lock() / rcu_read_unlock() pair around
dma_fence_get_status() and dma_fence_is_signaled() still needed?

> diff --git a/drivers/gpu/drm/i915/i915_request.c b/drivers/gpu/drm/i915/i915_request.c
> index f66f8efc70629..ce9aff50c0885 100644
> --- a/drivers/gpu/drm/i915/i915_request.c
> +++ b/drivers/gpu/drm/i915/i915_request.c
> @@ -2183,7 +2183,8 @@ void i915_request_show(struct drm_printer *m,
>  		       const char *prefix,
>  		       int indent)
>  {
> -	const char __rcu *timeline;
> +	const unsigned short len = 64;
> +	char timeline[len];
>  	char buf[80] = "";
>  	int x = 0;
>  
> @@ -2219,8 +2220,7 @@ void i915_request_show(struct drm_printer *m,
>  
>  	x = print_sched_attr(&rq->sched.attr, buf, x, sizeof(buf));
>  
> -	rcu_read_lock();
> -	timeline = dma_fence_timeline_name((struct dma_fence *)&rq->fence);
> +	dma_fence_timeline_name((struct dma_fence *)&rq->fence, timeline, len);
>  	drm_printf(m, "%s%.*s%c %llx:%lld%s%s %s @ %dms: %s\n",
>  		   prefix, indent, "                ",
>  		   queue_status(rq),
> @@ -2229,8 +2229,7 @@ void i915_request_show(struct drm_printer *m,
>  		   fence_status(rq),
>  		   buf,
>  		   jiffies_to_msecs(jiffies - rq->emitted_jiffies),
> -		   rcu_dereference(timeline));
> -	rcu_read_unlock();
> +		   timeline);
>  }
>  
>  static bool engine_match_ring(struct intel_engine_cs *engine, struct i915_request *rq)

[Severity: High]
Does calling dma_fence_timeline_name() in i915_request_show() introduce a
lock ordering inversion between rq->lock and sched_engine->lock (or
ce->guc_state.lock)?

Each i915_request initializes rq->fence with &rq->lock. During request
signaling in i915_request_retire() and signal_irq_work(), rq->lock is held
while invoking fence callbacks:

  i915_request_retire() / signal_irq_work() [holds rq->lock]
    -> dma_fence_signal_locked(&rq->fence)
      -> dma_i915_sw_fence_wake()
        -> submit_notify()
          -> execlists_submit_request() [acquires sched_engine->lock]
          -> guc_submit_request() [acquires ce->guc_state.lock]

However, i915_request_show() is called while holding sched_engine->lock or
ce->guc_state.lock in paths such as:

  intel_execlists_show_requests() / intel_execlists_dump_active_requests() [holds sched_engine->lock]
    -> i915_request_show()
      -> dma_fence_timeline_name() [acquires rq->lock]

  intel_guc_dump_active_requests() [holds ce->guc_state.lock]
    -> intel_engine_dump_active_requests()
      -> engine_dump_request()
        -> i915_request_show()
          -> dma_fence_timeline_name() [acquires rq->lock]

Can this cause an AB-BA deadlock when engine state is dumped concurrently
with request retirement or breadcrumb signaling?

[ ... ]

> diff --git a/include/trace/events/dma_fence.h b/include/trace/events/dma_fence.h
> index 5b10a9e06fb4e..f3b929b98cf42 100644
> --- a/include/trace/events/dma_fence.h
> +++ b/include/trace/events/dma_fence.h
> @@ -9,6 +9,8 @@
>  
>  struct dma_fence;
>  
> +#define FENCE_NAME_LEN 64
> +
>  DECLARE_EVENT_CLASS(dma_fence,
>  
>  	TP_PROTO(struct dma_fence *fence),
> @@ -16,21 +18,21 @@ DECLARE_EVENT_CLASS(dma_fence,
>  	TP_ARGS(fence),
>  
>  	TP_STRUCT__entry(
> -		__string(driver, dma_fence_driver_name(fence))
> -		__string(timeline, dma_fence_timeline_name(fence))
> +		__array(char, driver, FENCE_NAME_LEN)
> +		__array(char, timeline, FENCE_NAME_LEN)
>  		__field(unsigned int, context)
>  		__field(unsigned int, seqno)
>  	),
>  
>  	TP_fast_assign(
> -		__assign_str(driver);
> -		__assign_str(timeline);
> +		dma_fence_driver_name(fence, __entry->driver, FENCE_NAME_LEN);
> +		dma_fence_timeline_name(fence, __entry->timeline, FENCE_NAME_LEN);
>  		__entry->context = fence->context;
>  		__entry->seqno = fence->seqno;
>  	),

[Severity: High]
As noted above in dma_fence_driver_name(), calling dma_fence_driver_name()
and dma_fence_timeline_name() inside TP_fast_assign() acquires the fence
spinlock during trace_dma_fence_destroy(fence), which can deadlock when
dma_fence_put() is called under the fence lock.

>  
>  	TP_printk("driver=%s timeline=%s context=%u seqno=%u",
> -		  __get_str(driver), __get_str(timeline), __entry->context,
> +		  __entry->driver, __entry->timeline, __entry->context,
>  		  __entry->seqno)
>  );
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020100034.759183-2-phasta@kernel.org?part=3

  reply	other threads:[~2026-10-20 10:27 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20 10:00 [RFC PATCH v2 0/3] Solve dma_fence's problems by locking Philipp Stanner
2026-10-20 10:00 ` [RFC PATCH v2 1/3] dma-fence: Remove RCU protection for fence->ops Philipp Stanner
2026-10-20 10:20   ` sashiko-bot
2026-10-20 10:00 ` [RFC PATCH v2 2/3] dma-fence: use lock to protect against module-unload Philipp Stanner
2026-10-20 10:19   ` sashiko-bot
2026-10-20 10:00 ` [RFC PATCH v2 3/3] dma-fence: Support unload for ops->get_XXX_name() Philipp Stanner
2026-10-20 10:27   ` sashiko-bot [this message]
2026-10-20 11:48 ` ✓ i915.CI.BAT: success for Solve dma_fence's problems by locking Patchwork
2026-10-20 12:02 ` [RFC PATCH v2 0/3] " Christian König
2026-10-20 14:41   ` Philipp Stanner
2026-10-20 15:07     ` Christian König
2026-10-20 15:36       ` Philipp Stanner
2026-10-20 15:36 ` ✗ i915.CI.Full: failure for " Patchwork

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-178830@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=gary@garyguo.net \
    --cc=intel-gfx@lists.freedesktop.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=media-ci@linuxtv.org \
    --cc=ojeda@kernel.org \
    --cc=phasta@kernel.org \
    --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.