On Wed, May 14, 2025 at 7:58 AM Tvrtko Ursulin
<tvrtko.ursu...@igalia.com> wrote:
>
>
> On 14/05/2025 14:57, Rob Clark wrote:
> > On Wed, May 14, 2025 at 3:01 AM Tvrtko Ursulin
> > <tvrtko.ursu...@igalia.com> wrote:
> >>
> >>
> >> On 13/05/2025 15:16, Rob Clark wrote:
> >>> On Fri, May 9, 2025 at 8:34 AM Tvrtko Ursulin <tvrtko.ursu...@igalia.com> 
> >>> 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_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.ursu...@igalia.com>
> >>>> ---
> >>>>    drivers/dma-buf/dma-fence.c | 69 +++++++++++++++++++++++++++++++++++++
> >>>>    include/linux/dma-fence.h   | 32 ++++++++++++-----
> >>>>    2 files changed, 93 insertions(+), 8 deletions(-)
> >>>>
> >>>> diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
> >>>> index dc2456f68685..cfe1d7b79c22 100644
> >>>> --- a/drivers/dma-buf/dma-fence.c
> >>>> +++ b/drivers/dma-buf/dma-fence.c
> >>>> @@ -533,6 +533,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) &&
> >>>> @@ -560,6 +561,8 @@ void dma_fence_release(struct kref *kref)
> >>>>                   fence->ops->release(fence);
> >>>>           else
> >>>>                   dma_fence_free(fence);
> >>>> +
> >>>> +       dma_fence_access_end();
> >>>>    }
> >>>>    EXPORT_SYMBOL(dma_fence_release);
> >>>>
> >>>> @@ -982,11 +985,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);
> >>>>
> >>>> @@ -1033,3 +1038,67 @@ dma_fence_init64(struct dma_fence *fence, const 
> >>>> struct dma_fence_ops *ops,
> >>>>           __set_bit(DMA_FENCE_FLAG_SEQNO64_BIT, &fence->flags);
> >>>>    }
> >>>>    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(),
> >>>> +                        "rcu_read_lock() 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(),
> >>>> +                        "rcu_read_lock() 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";
> >>>
> >>> This means that trace_dma_fence_signaled() will get the wrong
> >>> timeline/driver name, which probably screws up perfetto and maybe
> >>> other tools.
> >>
> >> Do you think context and seqno are not enough for those tools and they
> >> actually rely on the names? It would sound weird if they decided to
> >> index anything on the names which are non-standardised between drivers,
> >> but I guess anything is possible.
> >
> > At some point perfetto uses the timeline name to put up a named fence
> > timeline, I'm not sure if it is using the name or context # for
> > subsequent fence events (namely, signalled).  I'd have to check the
> > code and get back to you.
>
> If you can it would be useful. Presumably it saves the names from the
> start edge of fence lifetime. But again, who knows.

Ok, it looks like perfetto is ok... mostly..
DrmTracker::GetFenceTimelineByContext() will try to lookup the
timeline by context #, and then if that fails, create a new timeline
with the name from the trace event, and add it to the hashmap.

It might be that "signaled-timeline" shows up if the first event seen
is the fence-signaled event.

> > There is also gpuvis, which I guess does something similar, but
> > haven't looked into it.  Idk if there are others.
>
> I know GpuVis uses DRM sched tracepoints since Pierre-Eric was
> explaining me about those in the context of tracing rework he did there.
> I am not sure about dma-fence tracepoints.
>
> +Pierre-Eric on the off chance you know from the top of your head how
> much GpuVis depends on them (dma-fence tracepoints).
>
> >>> Maybe it would work well enough just to move the
> >>> trace_dma_fence_signaled() call ahead of the test_and_set_bit()?  Idk
> >>> if some things will start getting confused if they see that trace
> >>> multiple times.
> >>
> >> Another alternative is to make this tracepoint access the names
> >> directly. It is under the lock so guaranteed not to get freed with
> >> drivers which will be made compliant with the documented rules.
> >
> > I guess it would have been better if, other than dma_fence_init
> > tracepoint, later tracepoints didn't include the driver/timeline
> > name.. that would have forced the use of the context.  But I guess too
> > late for that.  Perhaps the least bad thing to do is use the locking?
>
> You mean this last alternative I mentioned? I think that will work fine.
> I'll wait a little bit longer for more potential comments before re-spi
> ning with that.

yes

> Were you able to test the series for your use case? Assuming it is not
> upstream msm since I don't immediately see a path in msm_fence which
> gets freed at runtime?

Not yet, but I think it should because it is the exact same problem
your igt test triggers.

This is with my VM_BIND series, which will dynamically create/teardown
sched entities

BR,
-R

Reply via email to