[PATCH v6 3/4] dma-fence: Add safe access helpers and document the rules

Tvrtko Ursulin tvrtko.ursulin at igalia.com
Wed Jun 11 14:08:55 UTC 2025


On 11/06/2025 12:43, Christian König wrote:
> On 6/10/25 18:42, 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
>> rcu_read_lock() and rcu_read_unlock().
>>
>> 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 rcu_read_unlock().
>>
>> 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 at igalia.com>
> 
> Reviewed-by: Christian König <christian.koenig at amd.com>

Thanks Christian!

I have pinged xe maintainers for acks to merge the whole series via 
drm-misc-next and pending positive reply I can push it all.

Regards,

Tvrtko

> 
>> ---
>>   drivers/dma-buf/dma-fence.c      | 111 ++++++++++++++++++++++++++++---
>>   include/linux/dma-fence.h        |  31 ++++++---
>>   include/trace/events/dma_fence.h |  38 +++++++++--
>>   3 files changed, 157 insertions(+), 23 deletions(-)
>>
>> diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
>> index 74f9e4b665e3..3f78c56b58dc 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()) {
>> +		rcu_read_lock();
>> +		trace_dma_fence_wait_start(fence);
>> +		rcu_read_unlock();
>> +	}
>>   	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()) {
>> +		rcu_read_lock();
>> +		trace_dma_fence_wait_end(fence);
>> +		rcu_read_unlock();
>> +	}
>>   	return ret;
>>   }
>>   EXPORT_SYMBOL(dma_fence_wait_timeout);
>> @@ -533,16 +541,23 @@ void dma_fence_release(struct kref *kref)
>>   	struct dma_fence *fence =
>>   		container_of(kref, struct dma_fence, refcount);
>>   
>> +	rcu_read_lock();
>>   	trace_dma_fence_destroy(fence);
>>   
>> -	if (WARN(!list_empty(&fence->cb_list) &&
>> -		 !test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags),
>> -		 "Fence %s:%s:%llx:%llx released with pending signals!\n",
>> -		 dma_fence_driver_name(fence),
>> -		 dma_fence_timeline_name(fence),
>> -		 fence->context, fence->seqno)) {
>> +	if (!list_empty(&fence->cb_list) &&
>> +	    !test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) {
>> +		const char __rcu *timeline;
>> +		const char __rcu *driver;
>>   		unsigned long flags;
>>   
>> +		driver = dma_fence_driver_name(fence);
>> +		timeline = dma_fence_timeline_name(fence);
>> +
>> +		WARN(1,
>> +		     "Fence %s:%s:%llx:%llx released with pending signals!\n",
>> +		     rcu_dereference(driver), rcu_dereference(timeline),
>> +		     fence->context, fence->seqno);
>> +
>>   		/*
>>   		 * Failed to signal before release, likely a refcounting issue.
>>   		 *
>> @@ -556,6 +571,8 @@ void dma_fence_release(struct kref *kref)
>>   		spin_unlock_irqrestore(fence->lock, flags);
>>   	}
>>   
>> +	rcu_read_unlock();
>> +
>>   	if (fence->ops->release)
>>   		fence->ops->release(fence);
>>   	else
>> @@ -982,11 +999,21 @@ 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 *driver;
>> +
>> +	rcu_read_lock();
>> +
>> +	timeline = dma_fence_timeline_name(fence);
>> +	driver = dma_fence_driver_name(fence);
>> +
>>   	seq_printf(seq, "%s %s seq %llu %ssignalled\n",
>> -		   dma_fence_driver_name(fence),
>> -		   dma_fence_timeline_name(fence),
>> +		   rcu_dereference(driver),
>> +		   rcu_dereference(timeline),
>>   		   fence->seqno,
>>   		   dma_fence_is_signaled(fence) ? "" : "un");
>> +
>> +	rcu_read_unlock();
>>   }
>>   EXPORT_SYMBOL(dma_fence_describe);
>>   
>> @@ -1055,3 +1082,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 &rcu_read_lock and &rcu_read_unlock pair.
>> + */
>> +const char __rcu *dma_fence_driver_name(struct dma_fence *fence)
>> +{
>> +	RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
>> +			 "RCU protection is 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 &rcu_read_lock and &rcu_read_unlock pair.
>> + */
>> +const char __rcu *dma_fence_timeline_name(struct dma_fence *fence)
>> +{
>> +	RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
>> +			 "RCU protection is 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..64639e104110 100644
>> --- a/include/linux/dma-fence.h
>> +++ b/include/linux/dma-fence.h
>> @@ -378,15 +378,28 @@ 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);
>> -}
>> -
>> -static inline const char *dma_fence_timeline_name(struct dma_fence *fence)
>> -{
>> -	return fence->ops->get_timeline_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
>> + * &rcu_read_lock and &rcu_read_unlock 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.
>> + *
>> + */
>> +const char __rcu *dma_fence_driver_name(struct dma_fence *fence);
>> +const char __rcu *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),
>>   
> 



More information about the Intel-gfx mailing list