On Thu, Oct 29, 2026 at 10:55:42AM +0100, Philipp Stanner wrote:
> dma_fence_is_signaled() contains a lockless check of the signaled-bit,
> which is set under lock protection. This is a race condition which makes
> the API fragile and has caused at least two bugs already, addressed in:
>
> commit c8a5d5ea3ba6 ("nouveau: fix client work fence deletion race")
> commit 7c703a7d3f2b ("drm/amdgpu: Fix one use-after-free of VM")
>
> It has been argued by some that this race cannot be closed for the
> entire kernel through locking because this would lead to a cache line
> invalidation for all parties reading that fence. While it remains
> unclear why these parties don't simply let themselves be notified about
> the fence being signaled instead of checking this repeatedly manually,
> there is a way to preserve this behavior in any case:
>
> This patch adds a new flag for dma_fence_init(),
> DMA_FENCE_INIT_FLAG_RACY_SIGNALED_CHECK. If this flag is not set, the
> spinlock is taken and dma_fence_is_signaled() becomes perfectly
> synchronized: it can only return true once signaling of the fence is
> completed.
>
> Disable the racy check for everyone by default. Should some parties
> really experience severe performance degredations because of this, they
> can trivially re-enable the race condition for their driver by setting
> the flag in dma_fence_init().
>
My 2 cents - anyone arguing about cachelines probably has broken code to
begin with. I'd say don't give anyone an option to opt into a racey
check, if this regressing some peoples code the answer is likely
structurally fix your broken code.
Matt
> Signed-off-by: Philipp Stanner <[email protected]>
> ---
> drivers/dma-buf/dma-fence.c | 2 ++
> include/linux/dma-fence.h | 22 ++++++++++++++++++++++
> 2 files changed, 24 insertions(+)
>
> diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
> index f7c084926ae6..1ae8c7aade39 100644
> --- a/drivers/dma-buf/dma-fence.c
> +++ b/drivers/dma-buf/dma-fence.c
> @@ -1119,6 +1119,8 @@ dma_fence_init(struct dma_fence *fence, const struct
> dma_fence_ops *ops,
> /* Mapp the init_flags to the fence-internal flags. */
> if (test_bit(DMA_FENCE_INIT_FLAG_SEQNO64, &init_flags))
> set_bit(DMA_FENCE_FLAG_SEQNO64_BIT, &fence_flag_bits);
> + if (test_bit(DMA_FENCE_INIT_FLAG_RACY_SIGNALED_CHECK, &init_flags))
> + set_bit(DMA_FENCE_FLAG_RACY_SIGNALED_CHECK_BIT,
> &fence_flag_bits);
>
> __dma_fence_init(fence, ops, lock, context, seqno, fence_flag_bits);
> }
> diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h
> index 52e7ddfd2d06..ee6e8de3ebc6 100644
> --- a/include/linux/dma-fence.h
> +++ b/include/linux/dma-fence.h
> @@ -115,6 +115,7 @@ enum dma_fence_flag_bits {
> DMA_FENCE_FLAG_SIGNALED_BIT,
> DMA_FENCE_FLAG_TIMESTAMP_BIT,
> DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
> + DMA_FENCE_FLAG_RACY_SIGNALED_CHECK_BIT,
>
> DMA_FENCE_FLAG_USER_BITS, /* must always be last member */
> };
> @@ -124,9 +125,12 @@ enum dma_fence_flag_bits {
> * These flags can be passed in dma_fence_init().
> *
> * @DMA_FENCE_INIT_FLAG_SEQNO64: Use 64-bit sequence numbers.
> + * @DMA_FENCE_INIT_FLAG_RACY_SIGNALED_CHECK:
> + * Legacy flag. Enables a race condition in dma_fence_is_signaled().
> */
> enum dma_fence_init_flags {
> DMA_FENCE_INIT_FLAG_SEQNO64,
> + DMA_FENCE_INIT_FLAG_RACY_SIGNALED_CHECK,
>
> /* Must always be last member. */
> DMA_FENCE_INIT_FLAG_NR_OF_FLAGs,
> @@ -568,9 +572,27 @@ dma_fence_is_signaled_locked(struct dma_fence *fence)
> static inline bool
> dma_fence_is_signaled(struct dma_fence *fence)
> {
> + const bool racy_check = test_bit(DMA_FENCE_FLAG_RACY_SIGNALED_CHECK_BIT,
> + &fence->flags);
> const struct dma_fence_ops *ops;
> + bool signaled = false;
> + unsigned long flags;
>
> + /*
> + * For legacy reasons, there is a race window here against parties
> + * signaling the fence. They might still be doing that right now.
> + *
> + * For most users, this window shall be closed, but there are parties
> + * for whom the cache-line invalidation by taking the lock might be so
> + * significant that they prefer to accept the race at their own risk.
> + */
> + if (!racy_check)
> + dma_fence_lock_irqsave(fence, flags);
> if (dma_fence_test_signaled_flag(fence))
> + signaled = true;
> + if (!racy_check)
> + dma_fence_unlock_irqrestore(fence, flags);
> + if (signaled)
> return true;
>
> rcu_read_lock();
> --
> 2.56.0
>