* [PATCH v5 0/4] Fixing some dma-fence use-after-free
@ 2025-06-09 11:03 Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules Tvrtko Ursulin
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Tvrtko Ursulin @ 2025-06-09 11:03 UTC (permalink / raw)
To: dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Christian König, Matthew Brost, Lucas De Marchi,
Rodrigo Vivi, amd-gfx, intel-xe, intel-gfx, linux-media,
linaro-mm-sig, Tvrtko Ursulin
Hi all,
tl;dr;
Xe and probably some other drivers can tear down the internal state referenced
by an exported sync_file fence which then causes a null pointer derefences on
accessing said fence.
IGT that exploits the problem:
https://patchwork.freedesktop.org/patch/642709/?series=146211&rev=2
It seems there is a consensus this is a known problem with the dma-fence design,
where internal state shouldn't really be accessed after the fence has been
signaled. However currently the code is mostly unaware of that hence the use-
after-free potential.
To fix it, between the option of adding more reference counting and trying to
"revoke" the fence, suggestion is to focus on the later.
Reference to the recent discussion:
https://lore.kernel.org/dri-devel/20250418164246.72426-1-tvrtko.ursulin@igalia.com/
This series therefore attempts to implement a solution along those lines.
Most of the description what and how can be found in:
"dma-fence: Add safe access helpers and document the rules"
The series only addreses the runtime use-after-free scenarios,
such as the above explained situation with the xe driver. For now the module
unload problem is deliberately left for later. (Although again, some of the
patches merged earlier do make it safer, and will make future improvements
easier due fewer accesses to fence->ops.)
Final patch in the series is the one which makes xe compliant with the rules
and API proposed earlier in the series. It does so by ensuring there is at least
one RCU grace period between fences being signaled and driver allocated memory
accessible from xe fences getting freed. Which couples with the earlier (from
the series) refactors which added dma_fence_access_begin/end() protection to
the relevant call sites.
If this approach is acceptable the next steps will be to see if any other
drivers will need similar changes. And also to discuss whether we want to go a
step futher and later move to SRCU, so code would be protected against module
unload as well.
v2:
* Dropped module unload handling.
* Proposing real API instead of hacks.
v3:
* Dropped the dma_fence_is_array|chain ops to flags conversion.
* Dropped the i915 cleanup patch which is now independent.
* Squashed dma-fence helpers with internal usage patches.
* Restored missing hunk in "dma-fence: Use a flag for 64-bit seqnos".
* Removed the AMDGPU_JOB_GET_TIMELINE_NAME macro.
* Applied some r-b tags.
v4:
* Tidied 64-bit seqno flags patch and fixed for amdgpu user queues which landed
since.
* Adjusted some dma-fence tracepoints to avoid asserts.
* Protected tracepoints in dma_fence_wait_timeout() with the safe access
annotations.
* Dropped driver/timeline helper usage from amdgpu_trace.h.
* Dropped signaled fence protection from i915 timeline name vfunc.
v5:
* Rebased after prep patches merged.
* Move dma_fence_access_end() earlier in dma_fence_release(). (Christian)
Tvrtko Ursulin (4):
dma-fence: Add safe access helpers and document the rules
sync_file: Protect access to driver and timeline name
drm/i915: Protect access to driver and timeline name
drm/xe: Make dma-fences compliant with the safe access rules
drivers/dma-buf/dma-fence.c | 82 +++++++++++++++++++-
drivers/dma-buf/sync_file.c | 6 ++
drivers/gpu/drm/i915/gt/intel_gt_requests.c | 2 +
drivers/gpu/drm/i915/i915_request.c | 17 +---
drivers/gpu/drm/i915/i915_sw_fence.c | 2 +
drivers/gpu/drm/xe/xe_guc_exec_queue_types.h | 2 +
drivers/gpu/drm/xe/xe_guc_submit.c | 7 +-
drivers/gpu/drm/xe/xe_hw_fence.c | 3 +
include/linux/dma-fence.h | 32 ++++++--
include/trace/events/dma_fence.h | 38 ++++++++-
10 files changed, 162 insertions(+), 29 deletions(-)
--
2.48.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules
2025-06-09 11:03 [PATCH v5 0/4] Fixing some dma-fence use-after-free Tvrtko Ursulin
@ 2025-06-09 11:03 ` Tvrtko Ursulin
2025-06-10 13:27 ` Christian König
2025-06-09 11:03 ` [PATCH v5 2/4] sync_file: Protect access to driver and timeline name Tvrtko Ursulin
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Tvrtko Ursulin @ 2025-06-09 11:03 UTC (permalink / raw)
To: dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Christian König, Matthew Brost, Lucas De Marchi,
Rodrigo Vivi, amd-gfx, intel-xe, intel-gfx, linux-media,
linaro-mm-sig, Tvrtko Ursulin
Dma-fence objects currently suffer from a potential use after free problem
where fences exported to userspace and other drivers can outlive the
exporting driver, or the associated data structures.
The discussion on how to address this concluded that adding reference
counting to all the involved objects is not desirable, since it would need
to be very wide reaching and could cause unloadable drivers if another
entity would be holding onto a signaled fence reference potentially
indefinitely.
This patch enables the safe access by introducing and documenting a
contract between fence exporters and users. It documents a set of
contraints and adds helpers which a) drivers with potential to suffer from
the use after free must use and b) users of the dma-fence API must use as
well.
Premise of the design has multiple sides:
1. Drivers (fence exporters) MUST ensure a RCU grace period between
signalling a fence and freeing the driver private data associated with it.
The grace period does not have to follow the signalling immediately but
HAS to happen before data is freed.
2. Users of the dma-fence API marked with such requirement MUST contain
the complete access to the data within a single code block guarded by the
new dma_fence_access_begin() and dma_fence_access_end() helpers.
The combination of the two ensures that whoever sees the
DMA_FENCE_FLAG_SIGNALED_BIT not set is guaranteed to have access to a
valid fence->lock and valid data potentially accessed by the fence->ops
virtual functions, until the call to dma_fence_access_end().
3. Module unload (fence->ops) disappearing is for now explicitly not
handled. That would required a more complex protection, possibly needing
SRCU instead of RCU to handle callers such as dma_fence_release() and
dma_fence_wait_timeout(), where race between
dma_fence_enable_sw_signaling, signalling, and dereference of
fence->ops->wait() would need a sleeping SRCU context.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
---
drivers/dma-buf/dma-fence.c | 82 +++++++++++++++++++++++++++++++-
include/linux/dma-fence.h | 32 +++++++++----
include/trace/events/dma_fence.h | 38 +++++++++++++--
3 files changed, 138 insertions(+), 14 deletions(-)
diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
index 74f9e4b665e3..36604d68bdc8 100644
--- a/drivers/dma-buf/dma-fence.c
+++ b/drivers/dma-buf/dma-fence.c
@@ -511,12 +511,20 @@ dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)
dma_fence_enable_sw_signaling(fence);
- trace_dma_fence_wait_start(fence);
+ if (trace_dma_fence_wait_start_enabled()) {
+ dma_fence_access_begin();
+ trace_dma_fence_wait_start(fence);
+ dma_fence_access_end();
+ }
if (fence->ops->wait)
ret = fence->ops->wait(fence, intr, timeout);
else
ret = dma_fence_default_wait(fence, intr, timeout);
- trace_dma_fence_wait_end(fence);
+ if (trace_dma_fence_wait_end_enabled()) {
+ dma_fence_access_begin();
+ trace_dma_fence_wait_end(fence);
+ dma_fence_access_end();
+ }
return ret;
}
EXPORT_SYMBOL(dma_fence_wait_timeout);
@@ -533,6 +541,7 @@ void dma_fence_release(struct kref *kref)
struct dma_fence *fence =
container_of(kref, struct dma_fence, refcount);
+ dma_fence_access_begin();
trace_dma_fence_destroy(fence);
if (WARN(!list_empty(&fence->cb_list) &&
@@ -556,10 +565,13 @@ void dma_fence_release(struct kref *kref)
spin_unlock_irqrestore(fence->lock, flags);
}
+ dma_fence_access_end();
+
if (fence->ops->release)
fence->ops->release(fence);
else
dma_fence_free(fence);
+
}
EXPORT_SYMBOL(dma_fence_release);
@@ -982,11 +994,13 @@ EXPORT_SYMBOL(dma_fence_set_deadline);
*/
void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)
{
+ dma_fence_access_begin();
seq_printf(seq, "%s %s seq %llu %ssignalled\n",
dma_fence_driver_name(fence),
dma_fence_timeline_name(fence),
fence->seqno,
dma_fence_is_signaled(fence) ? "" : "un");
+ dma_fence_access_end();
}
EXPORT_SYMBOL(dma_fence_describe);
@@ -1055,3 +1069,67 @@ dma_fence_init64(struct dma_fence *fence, const struct dma_fence_ops *ops,
BIT(DMA_FENCE_FLAG_SEQNO64_BIT));
}
EXPORT_SYMBOL(dma_fence_init64);
+
+/**
+ * dma_fence_driver_name - Access the driver name
+ * @fence: the fence to query
+ *
+ * Returns a driver name backing the dma-fence implementation.
+ *
+ * IMPORTANT CONSIDERATION:
+ * Dma-fence contract stipulates that access to driver provided data (data not
+ * directly embedded into the object itself), such as the &dma_fence.lock and
+ * 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 &dma_fence_access_being and &dma_fence_access_end pair.
+ */
+const char *dma_fence_driver_name(struct dma_fence *fence)
+{
+ RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+ "dma_fence_access_begin/end() are required for safe access to returned string");
+
+ if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return fence->ops->get_driver_name(fence);
+ else
+ return "detached-driver";
+}
+EXPORT_SYMBOL(dma_fence_driver_name);
+
+/**
+ * dma_fence_timeline_name - Access the timeline name
+ * @fence: the fence to query
+ *
+ * Returns a timeline name provided by the dma-fence implementation.
+ *
+ * IMPORTANT CONSIDERATION:
+ * Dma-fence contract stipulates that access to driver provided data (data not
+ * directly embedded into the object itself), such as the &dma_fence.lock and
+ * 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 &dma_fence_access_being and &dma_fence_access_end pair.
+ */
+const char *dma_fence_timeline_name(struct dma_fence *fence)
+{
+ RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+ "dma_fence_access_begin/end() are required for safe access to returned string");
+
+ if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
+ return fence->ops->get_driver_name(fence);
+ else
+ return "signaled-timeline";
+}
+EXPORT_SYMBOL(dma_fence_timeline_name);
diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h
index 10a849cb2d3f..366da956fb85 100644
--- a/include/linux/dma-fence.h
+++ b/include/linux/dma-fence.h
@@ -378,15 +378,31 @@ bool dma_fence_remove_callback(struct dma_fence *fence,
struct dma_fence_cb *cb);
void dma_fence_enable_sw_signaling(struct dma_fence *fence);
-static inline const char *dma_fence_driver_name(struct dma_fence *fence)
-{
- return fence->ops->get_driver_name(fence);
-}
+/**
+ * DOC: Safe external access to driver provided object members
+ *
+ * All data not stored directly in the dma-fence object, such as the
+ * &dma_fence.lock and memory potentially accessed by functions in the
+ * &dma_fence.ops table, MUST NOT be accessed after the fence has been signalled
+ * because after that point drivers are allowed to free it.
+ *
+ * All code accessing that data via the dma-fence API (or directly, which is
+ * discouraged), MUST make sure to contain the complete access within a
+ * &dma_fence_access_begin and &dma_fence_access_end pair.
+ *
+ * Some dma-fence API handles this automatically, while other, as for example
+ * &dma_fence_driver_name and &dma_fence_timeline_name, leave that
+ * responsibility to the caller.
+ *
+ * To enable this scheme to work drivers MUST ensure a RCU grace period elapses
+ * between signalling the fence and freeing the said data.
+ *
+ */
+#define dma_fence_access_begin rcu_read_lock
+#define dma_fence_access_end rcu_read_unlock
-static inline const char *dma_fence_timeline_name(struct dma_fence *fence)
-{
- return fence->ops->get_timeline_name(fence);
-}
+const char *dma_fence_driver_name(struct dma_fence *fence);
+const char *dma_fence_timeline_name(struct dma_fence *fence);
/**
* dma_fence_is_signaled_locked - Return an indication if the fence
diff --git a/include/trace/events/dma_fence.h b/include/trace/events/dma_fence.h
index 84c83074ee81..4814a65b68dc 100644
--- a/include/trace/events/dma_fence.h
+++ b/include/trace/events/dma_fence.h
@@ -34,14 +34,44 @@ DECLARE_EVENT_CLASS(dma_fence,
__entry->seqno)
);
-DEFINE_EVENT(dma_fence, dma_fence_emit,
+/*
+ * Safe only for call sites which are guaranteed to not race with fence
+ * signaling,holding the fence->lock and having checked for not signaled, or the
+ * signaling path itself.
+ */
+DECLARE_EVENT_CLASS(dma_fence_unsignaled,
+
+ TP_PROTO(struct dma_fence *fence),
+
+ TP_ARGS(fence),
+
+ TP_STRUCT__entry(
+ __string(driver, fence->ops->get_driver_name(fence))
+ __string(timeline, fence->ops->get_timeline_name(fence))
+ __field(unsigned int, context)
+ __field(unsigned int, seqno)
+ ),
+
+ TP_fast_assign(
+ __assign_str(driver);
+ __assign_str(timeline);
+ __entry->context = fence->context;
+ __entry->seqno = fence->seqno;
+ ),
+
+ TP_printk("driver=%s timeline=%s context=%u seqno=%u",
+ __get_str(driver), __get_str(timeline), __entry->context,
+ __entry->seqno)
+);
+
+DEFINE_EVENT(dma_fence_unsignaled, dma_fence_emit,
TP_PROTO(struct dma_fence *fence),
TP_ARGS(fence)
);
-DEFINE_EVENT(dma_fence, dma_fence_init,
+DEFINE_EVENT(dma_fence_unsignaled, dma_fence_init,
TP_PROTO(struct dma_fence *fence),
@@ -55,14 +85,14 @@ DEFINE_EVENT(dma_fence, dma_fence_destroy,
TP_ARGS(fence)
);
-DEFINE_EVENT(dma_fence, dma_fence_enable_signal,
+DEFINE_EVENT(dma_fence_unsignaled, dma_fence_enable_signal,
TP_PROTO(struct dma_fence *fence),
TP_ARGS(fence)
);
-DEFINE_EVENT(dma_fence, dma_fence_signaled,
+DEFINE_EVENT(dma_fence_unsignaled, dma_fence_signaled,
TP_PROTO(struct dma_fence *fence),
--
2.48.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 2/4] sync_file: Protect access to driver and timeline name
2025-06-09 11:03 [PATCH v5 0/4] Fixing some dma-fence use-after-free Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules Tvrtko Ursulin
@ 2025-06-09 11:03 ` Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 3/4] drm/i915: " Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 4/4] drm/xe: Make dma-fences compliant with the safe access rules Tvrtko Ursulin
3 siblings, 0 replies; 6+ messages in thread
From: Tvrtko Ursulin @ 2025-06-09 11:03 UTC (permalink / raw)
To: dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Christian König, Matthew Brost, Lucas De Marchi,
Rodrigo Vivi, amd-gfx, intel-xe, intel-gfx, linux-media,
linaro-mm-sig, Tvrtko Ursulin
Protect the access to driver and timeline name which otherwise could be
freed as dma-fence exported is signalling fences.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
---
drivers/dma-buf/sync_file.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c
index 212df4b849fe..ad87116baa24 100644
--- a/drivers/dma-buf/sync_file.c
+++ b/drivers/dma-buf/sync_file.c
@@ -136,11 +136,13 @@ char *sync_file_get_name(struct sync_file *sync_file, char *buf, int len)
} else {
struct dma_fence *fence = sync_file->fence;
+ dma_fence_access_begin();
snprintf(buf, len, "%s-%s%llu-%lld",
dma_fence_driver_name(fence),
dma_fence_timeline_name(fence),
fence->context,
fence->seqno);
+ dma_fence_access_end();
}
return buf;
@@ -262,6 +264,8 @@ 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)
{
+ dma_fence_access_begin();
+
strscpy(info->obj_name, dma_fence_timeline_name(fence),
sizeof(info->obj_name));
strscpy(info->driver_name, dma_fence_driver_name(fence),
@@ -273,6 +277,8 @@ static int sync_fill_fence_info(struct dma_fence *fence,
ktime_to_ns(dma_fence_timestamp(fence)) :
ktime_set(0, 0);
+ dma_fence_access_end();
+
return info->status;
}
--
2.48.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 3/4] drm/i915: Protect access to driver and timeline name
2025-06-09 11:03 [PATCH v5 0/4] Fixing some dma-fence use-after-free Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 2/4] sync_file: Protect access to driver and timeline name Tvrtko Ursulin
@ 2025-06-09 11:03 ` Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 4/4] drm/xe: Make dma-fences compliant with the safe access rules Tvrtko Ursulin
3 siblings, 0 replies; 6+ messages in thread
From: Tvrtko Ursulin @ 2025-06-09 11:03 UTC (permalink / raw)
To: dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Christian König, Matthew Brost, Lucas De Marchi,
Rodrigo Vivi, amd-gfx, intel-xe, intel-gfx, linux-media,
linaro-mm-sig, Tvrtko Ursulin, Andi Shyti
Protect the access to driver and timeline name which otherwise could be
freed as dma-fence exported is signalling fences.
Now that the safe access is handled in the dma-fence API, the external
callers such as sync_file, and our internal code paths, we can drop the
similar protection from i915_fence_get_timeline_name().
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Reviewed-by: Andi Shyti <andi.shyti@linux.intel.com>
---
drivers/gpu/drm/i915/gt/intel_gt_requests.c | 2 ++
drivers/gpu/drm/i915/i915_request.c | 17 +++--------------
drivers/gpu/drm/i915/i915_sw_fence.c | 2 ++
3 files changed, 7 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/i915/gt/intel_gt_requests.c b/drivers/gpu/drm/i915/gt/intel_gt_requests.c
index ae3557ed6c1e..11fca24c8b5b 100644
--- a/drivers/gpu/drm/i915/gt/intel_gt_requests.c
+++ b/drivers/gpu/drm/i915/gt/intel_gt_requests.c
@@ -251,10 +251,12 @@ void intel_gt_watchdog_work(struct work_struct *work)
if (!i915_request_completed(rq)) {
struct dma_fence *f = &rq->fence;
+ dma_fence_access_begin();
pr_notice("Fence expiration time out i915-%s:%s:%llx!\n",
dma_fence_driver_name(f),
dma_fence_timeline_name(f),
f->seqno);
+ dma_fence_access_end();
i915_request_cancel(rq, -EINTR);
}
i915_request_put(rq);
diff --git a/drivers/gpu/drm/i915/i915_request.c b/drivers/gpu/drm/i915/i915_request.c
index 4874c4f1e4ab..a8de736ff556 100644
--- a/drivers/gpu/drm/i915/i915_request.c
+++ b/drivers/gpu/drm/i915/i915_request.c
@@ -66,18 +66,6 @@ static const char *i915_fence_get_timeline_name(struct dma_fence *fence)
{
const struct i915_gem_context *ctx;
- /*
- * The timeline struct (as part of the ppgtt underneath a context)
- * may be freed when the request is no longer in use by the GPU.
- * We could extend the life of a context to beyond that of all
- * fences, possibly keeping the hw resource around indefinitely,
- * or we just give them a false name. Since
- * dma_fence_ops.get_timeline_name is a debug feature, the occasional
- * lie seems justifiable.
- */
- if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
- return "signaled";
-
ctx = i915_request_gem_context(to_request(fence));
if (!ctx)
return "[" DRIVER_NAME "]";
@@ -2184,7 +2172,6 @@ void i915_request_show(struct drm_printer *m,
const char *prefix,
int indent)
{
- const char *name = dma_fence_timeline_name((struct dma_fence *)&rq->fence);
char buf[80] = "";
int x = 0;
@@ -2220,6 +2207,7 @@ void i915_request_show(struct drm_printer *m,
x = print_sched_attr(&rq->sched.attr, buf, x, sizeof(buf));
+ dma_fence_access_begin();
drm_printf(m, "%s%.*s%c %llx:%lld%s%s %s @ %dms: %s\n",
prefix, indent, " ",
queue_status(rq),
@@ -2228,7 +2216,8 @@ void i915_request_show(struct drm_printer *m,
fence_status(rq),
buf,
jiffies_to_msecs(jiffies - rq->emitted_jiffies),
- name);
+ dma_fence_timeline_name((struct dma_fence *)&rq->fence));
+ dma_fence_access_end();
}
static bool engine_match_ring(struct intel_engine_cs *engine, struct i915_request *rq)
diff --git a/drivers/gpu/drm/i915/i915_sw_fence.c b/drivers/gpu/drm/i915/i915_sw_fence.c
index 9edf659d18db..4cdb8dd524f3 100644
--- a/drivers/gpu/drm/i915/i915_sw_fence.c
+++ b/drivers/gpu/drm/i915/i915_sw_fence.c
@@ -435,11 +435,13 @@ static void timer_i915_sw_fence_wake(struct timer_list *t)
if (!fence)
return;
+ dma_fence_access_begin();
pr_notice("Asynchronous wait on fence %s:%s:%llx timed out (hint:%ps)\n",
dma_fence_driver_name(cb->dma),
dma_fence_timeline_name(cb->dma),
cb->dma->seqno,
i915_sw_fence_debug_hint(fence));
+ dma_fence_access_end();
i915_sw_fence_set_error_once(fence, -ETIMEDOUT);
i915_sw_fence_complete(fence);
--
2.48.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 4/4] drm/xe: Make dma-fences compliant with the safe access rules
2025-06-09 11:03 [PATCH v5 0/4] Fixing some dma-fence use-after-free Tvrtko Ursulin
` (2 preceding siblings ...)
2025-06-09 11:03 ` [PATCH v5 3/4] drm/i915: " Tvrtko Ursulin
@ 2025-06-09 11:03 ` Tvrtko Ursulin
3 siblings, 0 replies; 6+ messages in thread
From: Tvrtko Ursulin @ 2025-06-09 11:03 UTC (permalink / raw)
To: dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Christian König, Matthew Brost, Lucas De Marchi,
Rodrigo Vivi, amd-gfx, intel-xe, intel-gfx, linux-media,
linaro-mm-sig, Tvrtko Ursulin
Xe can free some of the data pointed to by the dma-fences it exports. Most
notably the timeline name can get freed if userspace closes the associated
submit queue. At the same time the fence could have been exported to a
third party (for example a sync_fence fd) which will then cause an use-
after-free on subsequent access.
To make this safe we need to make the driver compliant with the newly
documented dma-fence rules. Driver has to ensure a RCU grace period
between signalling a fence and freeing any data pointed to by said fence.
For the timeline name we simply make the queue be freed via kfree_rcu and
for the shared lock associated with multiple queues we add a RCU grace
period before freeing the per GT structure holding the lock.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/xe/xe_guc_exec_queue_types.h | 2 ++
drivers/gpu/drm/xe/xe_guc_submit.c | 7 ++++++-
drivers/gpu/drm/xe/xe_hw_fence.c | 3 +++
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h
index 4c39f01e4f52..a3f421e2adc0 100644
--- a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h
@@ -20,6 +20,8 @@ struct xe_exec_queue;
struct xe_guc_exec_queue {
/** @q: Backpointer to parent xe_exec_queue */
struct xe_exec_queue *q;
+ /** @rcu: For safe freeing of exported dma fences */
+ struct rcu_head rcu;
/** @sched: GPU scheduler for this xe_exec_queue */
struct xe_gpu_scheduler sched;
/** @entity: Scheduler entity for this xe_exec_queue */
diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index 2b61d017eeca..c7f45800ec6a 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -1299,7 +1299,11 @@ static void __guc_exec_queue_fini_async(struct work_struct *w)
xe_sched_entity_fini(&ge->entity);
xe_sched_fini(&ge->sched);
- kfree(ge);
+ /*
+ * RCU free due sched being exported via DRM scheduler fences
+ * (timeline name).
+ */
+ kfree_rcu(ge, rcu);
xe_exec_queue_fini(q);
xe_pm_runtime_put(guc_to_xe(guc));
}
@@ -1482,6 +1486,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
q->guc = ge;
ge->q = q;
+ init_rcu_head(&ge->rcu);
init_waitqueue_head(&ge->suspend_wait);
for (i = 0; i < MAX_STATIC_MSG_TYPE; ++i)
diff --git a/drivers/gpu/drm/xe/xe_hw_fence.c b/drivers/gpu/drm/xe/xe_hw_fence.c
index 03eb8c6d1616..b2a0c46dfcd4 100644
--- a/drivers/gpu/drm/xe/xe_hw_fence.c
+++ b/drivers/gpu/drm/xe/xe_hw_fence.c
@@ -100,6 +100,9 @@ void xe_hw_fence_irq_finish(struct xe_hw_fence_irq *irq)
spin_unlock_irqrestore(&irq->lock, flags);
dma_fence_end_signalling(tmp);
}
+
+ /* Safe release of the irq->lock used in dma_fence_init. */
+ synchronize_rcu();
}
void xe_hw_fence_irq_run(struct xe_hw_fence_irq *irq)
--
2.48.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules
2025-06-09 11:03 ` [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules Tvrtko Ursulin
@ 2025-06-10 13:27 ` Christian König
0 siblings, 0 replies; 6+ messages in thread
From: Christian König @ 2025-06-10 13:27 UTC (permalink / raw)
To: Tvrtko Ursulin, dri-devel
Cc: kernel-dev, Rob Clark, Sumit Semwal, Gustavo Padovan,
Matthew Brost, Lucas De Marchi, Rodrigo Vivi, amd-gfx, intel-xe,
intel-gfx, linux-media, linaro-mm-sig
On 6/9/25 13:03, Tvrtko Ursulin wrote:
> Dma-fence objects currently suffer from a potential use after free problem
> where fences exported to userspace and other drivers can outlive the
> exporting driver, or the associated data structures.
>
> The discussion on how to address this concluded that adding reference
> counting to all the involved objects is not desirable, since it would need
> to be very wide reaching and could cause unloadable drivers if another
> entity would be holding onto a signaled fence reference potentially
> indefinitely.
>
> This patch enables the safe access by introducing and documenting a
> contract between fence exporters and users. It documents a set of
> contraints and adds helpers which a) drivers with potential to suffer from
> the use after free must use and b) users of the dma-fence API must use as
> well.
>
> Premise of the design has multiple sides:
>
> 1. Drivers (fence exporters) MUST ensure a RCU grace period between
> signalling a fence and freeing the driver private data associated with it.
>
> The grace period does not have to follow the signalling immediately but
> HAS to happen before data is freed.
>
> 2. Users of the dma-fence API marked with such requirement MUST contain
> the complete access to the data within a single code block guarded by the
> new dma_fence_access_begin() and dma_fence_access_end() helpers.
>
> The combination of the two ensures that whoever sees the
> DMA_FENCE_FLAG_SIGNALED_BIT not set is guaranteed to have access to a
> valid fence->lock and valid data potentially accessed by the fence->ops
> virtual functions, until the call to dma_fence_access_end().
>
> 3. Module unload (fence->ops) disappearing is for now explicitly not
> handled. That would required a more complex protection, possibly needing
> SRCU instead of RCU to handle callers such as dma_fence_release() and
> dma_fence_wait_timeout(), where race between
> dma_fence_enable_sw_signaling, signalling, and dereference of
> fence->ops->wait() would need a sleeping SRCU context.
>
> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> ---
> drivers/dma-buf/dma-fence.c | 82 +++++++++++++++++++++++++++++++-
> include/linux/dma-fence.h | 32 +++++++++----
> include/trace/events/dma_fence.h | 38 +++++++++++++--
> 3 files changed, 138 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
> index 74f9e4b665e3..36604d68bdc8 100644
> --- a/drivers/dma-buf/dma-fence.c
> +++ b/drivers/dma-buf/dma-fence.c
> @@ -511,12 +511,20 @@ dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)
>
> dma_fence_enable_sw_signaling(fence);
>
> - trace_dma_fence_wait_start(fence);
> + if (trace_dma_fence_wait_start_enabled()) {
> + dma_fence_access_begin();
> + trace_dma_fence_wait_start(fence);
> + dma_fence_access_end();
> + }
> if (fence->ops->wait)
> ret = fence->ops->wait(fence, intr, timeout);
> else
> ret = dma_fence_default_wait(fence, intr, timeout);
> - trace_dma_fence_wait_end(fence);
> + if (trace_dma_fence_wait_end_enabled()) {
> + dma_fence_access_begin();
> + trace_dma_fence_wait_end(fence);
> + dma_fence_access_end();
> + }
> return ret;
> }
> EXPORT_SYMBOL(dma_fence_wait_timeout);
> @@ -533,6 +541,7 @@ void dma_fence_release(struct kref *kref)
> struct dma_fence *fence =
> container_of(kref, struct dma_fence, refcount);
>
> + dma_fence_access_begin();
> trace_dma_fence_destroy(fence);
>
> if (WARN(!list_empty(&fence->cb_list) &&
> @@ -556,10 +565,13 @@ void dma_fence_release(struct kref *kref)
> spin_unlock_irqrestore(fence->lock, flags);
> }
>
> + dma_fence_access_end();
> +
> if (fence->ops->release)
> fence->ops->release(fence);
> else
> dma_fence_free(fence);
> +
> }
> EXPORT_SYMBOL(dma_fence_release);
>
> @@ -982,11 +994,13 @@ EXPORT_SYMBOL(dma_fence_set_deadline);
> */
> void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)
> {
> + dma_fence_access_begin();
> seq_printf(seq, "%s %s seq %llu %ssignalled\n",
> dma_fence_driver_name(fence),
> dma_fence_timeline_name(fence),
> fence->seqno,
> dma_fence_is_signaled(fence) ? "" : "un");
> + dma_fence_access_end();
> }
> EXPORT_SYMBOL(dma_fence_describe);
>
> @@ -1055,3 +1069,67 @@ dma_fence_init64(struct dma_fence *fence, const struct dma_fence_ops *ops,
> BIT(DMA_FENCE_FLAG_SEQNO64_BIT));
> }
> EXPORT_SYMBOL(dma_fence_init64);
> +
> +/**
> + * dma_fence_driver_name - Access the driver name
> + * @fence: the fence to query
> + *
> + * Returns a driver name backing the dma-fence implementation.
> + *
> + * IMPORTANT CONSIDERATION:
> + * Dma-fence contract stipulates that access to driver provided data (data not
> + * directly embedded into the object itself), such as the &dma_fence.lock and
> + * 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 &dma_fence_access_being and &dma_fence_access_end pair.
> + */
> +const char *dma_fence_driver_name(struct dma_fence *fence)
> +{
> + RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
> + "dma_fence_access_begin/end() are required for safe access to returned string");
> +
> + if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
> + return fence->ops->get_driver_name(fence);
> + else
> + return "detached-driver";
> +}
> +EXPORT_SYMBOL(dma_fence_driver_name);
> +
> +/**
> + * dma_fence_timeline_name - Access the timeline name
> + * @fence: the fence to query
> + *
> + * Returns a timeline name provided by the dma-fence implementation.
> + *
> + * IMPORTANT CONSIDERATION:
> + * Dma-fence contract stipulates that access to driver provided data (data not
> + * directly embedded into the object itself), such as the &dma_fence.lock and
> + * 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 &dma_fence_access_being and &dma_fence_access_end pair.
> + */
> +const char *dma_fence_timeline_name(struct dma_fence *fence)
> +{
> + RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
> + "dma_fence_access_begin/end() are required for safe access to returned string");
> +
> + if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
> + return fence->ops->get_driver_name(fence);
> + else
> + return "signaled-timeline";
> +}
> +EXPORT_SYMBOL(dma_fence_timeline_name);
> diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h
> index 10a849cb2d3f..366da956fb85 100644
> --- a/include/linux/dma-fence.h
> +++ b/include/linux/dma-fence.h
> @@ -378,15 +378,31 @@ bool dma_fence_remove_callback(struct dma_fence *fence,
> struct dma_fence_cb *cb);
> void dma_fence_enable_sw_signaling(struct dma_fence *fence);
>
> -static inline const char *dma_fence_driver_name(struct dma_fence *fence)
> -{
> - return fence->ops->get_driver_name(fence);
> -}
> +/**
> + * DOC: Safe external access to driver provided object members
> + *
> + * All data not stored directly in the dma-fence object, such as the
> + * &dma_fence.lock and memory potentially accessed by functions in the
> + * &dma_fence.ops table, MUST NOT be accessed after the fence has been signalled
> + * because after that point drivers are allowed to free it.
> + *
> + * All code accessing that data via the dma-fence API (or directly, which is
> + * discouraged), MUST make sure to contain the complete access within a
> + * &dma_fence_access_begin and &dma_fence_access_end pair.
> + *
> + * Some dma-fence API handles this automatically, while other, as for example
> + * &dma_fence_driver_name and &dma_fence_timeline_name, leave that
> + * responsibility to the caller.
> + *
> + * To enable this scheme to work drivers MUST ensure a RCU grace period elapses
> + * between signalling the fence and freeing the said data.
> + *
> + */
> +#define dma_fence_access_begin rcu_read_lock
> +#define dma_fence_access_end rcu_read_unlock
Please drop those in a favor of another change and use rcu_read_lock/unlock in the code directly.
But see below.
>
> -static inline const char *dma_fence_timeline_name(struct dma_fence *fence)
> -{
> - return fence->ops->get_timeline_name(fence);
> -}
> +const char *dma_fence_driver_name(struct dma_fence *fence);
> +const char *dma_fence_timeline_name(struct dma_fence *fence);
Please declare those as:
const char __rcu *dma_fence_driver_name(struct dma_fence *fence);
const char __rcu *dma_fence_timeline_name(struct dma_fence *fence);
And then use rcu_dereference() on the return value when calling those functions.
This allows the automated checker to complain when the functions/return value isn't used correctly.
Regards,
Christian.
>
> /**
> * dma_fence_is_signaled_locked - Return an indication if the fence
> diff --git a/include/trace/events/dma_fence.h b/include/trace/events/dma_fence.h
> index 84c83074ee81..4814a65b68dc 100644
> --- a/include/trace/events/dma_fence.h
> +++ b/include/trace/events/dma_fence.h
> @@ -34,14 +34,44 @@ DECLARE_EVENT_CLASS(dma_fence,
> __entry->seqno)
> );
>
> -DEFINE_EVENT(dma_fence, dma_fence_emit,
> +/*
> + * Safe only for call sites which are guaranteed to not race with fence
> + * signaling,holding the fence->lock and having checked for not signaled, or the
> + * signaling path itself.
> + */
> +DECLARE_EVENT_CLASS(dma_fence_unsignaled,
> +
> + TP_PROTO(struct dma_fence *fence),
> +
> + TP_ARGS(fence),
> +
> + TP_STRUCT__entry(
> + __string(driver, fence->ops->get_driver_name(fence))
> + __string(timeline, fence->ops->get_timeline_name(fence))
> + __field(unsigned int, context)
> + __field(unsigned int, seqno)
> + ),
> +
> + TP_fast_assign(
> + __assign_str(driver);
> + __assign_str(timeline);
> + __entry->context = fence->context;
> + __entry->seqno = fence->seqno;
> + ),
> +
> + TP_printk("driver=%s timeline=%s context=%u seqno=%u",
> + __get_str(driver), __get_str(timeline), __entry->context,
> + __entry->seqno)
> +);
> +
> +DEFINE_EVENT(dma_fence_unsignaled, dma_fence_emit,
>
> TP_PROTO(struct dma_fence *fence),
>
> TP_ARGS(fence)
> );
>
> -DEFINE_EVENT(dma_fence, dma_fence_init,
> +DEFINE_EVENT(dma_fence_unsignaled, dma_fence_init,
>
> TP_PROTO(struct dma_fence *fence),
>
> @@ -55,14 +85,14 @@ DEFINE_EVENT(dma_fence, dma_fence_destroy,
> TP_ARGS(fence)
> );
>
> -DEFINE_EVENT(dma_fence, dma_fence_enable_signal,
> +DEFINE_EVENT(dma_fence_unsignaled, dma_fence_enable_signal,
>
> TP_PROTO(struct dma_fence *fence),
>
> TP_ARGS(fence)
> );
>
> -DEFINE_EVENT(dma_fence, dma_fence_signaled,
> +DEFINE_EVENT(dma_fence_unsignaled, dma_fence_signaled,
>
> TP_PROTO(struct dma_fence *fence),
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-06-10 13:27 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-09 11:03 [PATCH v5 0/4] Fixing some dma-fence use-after-free Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 1/4] dma-fence: Add safe access helpers and document the rules Tvrtko Ursulin
2025-06-10 13:27 ` Christian König
2025-06-09 11:03 ` [PATCH v5 2/4] sync_file: Protect access to driver and timeline name Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 3/4] drm/i915: " Tvrtko Ursulin
2025-06-09 11:03 ` [PATCH v5 4/4] drm/xe: Make dma-fences compliant with the safe access rules Tvrtko Ursulin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).