Re: [PATCH 6.12 528/602] thunderbolt: Prevent XDomain delayed work use-after-free on disconnect

Harshit Mogalapalli <[email protected]> Sat, 1 Aug 2026 20:56:42 +0530
Newsgroups dev.linux.lists.patches,org.kernel.vger.stable
Message-ID <[email protected]>
Hi Greg/Sasha,

On 30/07/26 7:45 pm, Greg Kroah-Hartman wrote:
> 6.12-stable review patch.  If anyone has any objections, please let me know.
> 
> ------------------
> 
> From: Michael Bommarito <[email protected]>
> 
> [ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ]
> 
> tb_xdp_handle_request() runs on system_wq and queues
> xd->state_work via queue_delayed_work() in three request handlers:
> PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake),
> and LINK_STATE_CHANGE_REQUEST.  Similarly, update_xdomain() queues
> xd->properties_changed_work when local properties change.
> 
> Concurrently, tb_xdomain_remove() calls stop_handshake() which does
> cancel_delayed_work_sync() on both delayed works.  Later,
> tb_xdomain_unregister() calls device_unregister() which eventually
> frees the xdomain.  Since commit 559c1e1e0134 ("thunderbolt: Run
> tb_xdp_handle_request() in system workqueue") moved the request
> handler off tb->wq, the handler and the remove path are no longer
> serialized.  If queue_delayed_work() executes after
> cancel_delayed_work_sync() but before the xdomain is freed, the
> delayed work fires on a freed object.
> 
> Add xd->removing that tb_xdomain_remove() sets under xd->lock
> before calling stop_handshake().  

let's remember this.

> Each external queue site holds
> the same lock and checks removing before calling
> queue_delayed_work().  This provides the mutual exclusion needed:
> either the queue site acquires the lock first and queues work that
> the subsequent cancel will see, or the remove path acquires the
> lock first and the queue site observes removing == true and skips
> the queue.
> 
> Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue")
> Cc: [email protected]
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito <[email protected]>
> Signed-off-by: Mika Westerberg <[email protected]>
> Signed-off-by: Sasha Levin <[email protected]>
> Signed-off-by: Greg Kroah-Hartman <[email protected]>
> ---
>   drivers/thunderbolt/xdomain.c |   26 +++++++++++++-------------
>   1 file changed, 13 insertions(+), 13 deletions(-)
> 
> --- a/drivers/thunderbolt/xdomain.c
> +++ b/drivers/thunderbolt/xdomain.c
> @@ -905,6 +905,19 @@ void tb_unregister_service_driver(struct
>   }
>   EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
>   
> +static int update_xdomain(struct device *dev, void *data)
> +{
> +	struct tb_xdomain *xd;
> +
> +	xd = tb_to_xdomain(dev);
> +	if (xd) {
> +		queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
> +				   msecs_to_jiffies(50));
> +	}
> +
> +	return 0;
> +}
> +


I have run an AI assisted backport review and it spotted an issue. I
have taken a look, and this backport does not contain the fix described
by upstream 2c5d2d3c3f70.

Upstream adds a removing flag and protects queueing, for example:

     mutex_lock(&xd->lock);
     if (!xd->removing)
             queue_delayed_work(xd->tb->wq,
                                &xd->properties_changed_work, ...);
     mutex_unlock(&xd->lock);

It also sets xd->removing under the same lock before
stop_handshake() cancels delayed work.

The 6.12.y commit still has:

     if (xd)
             queue_delayed_work(xd->tb->wq,
                                &xd->properties_changed_work, ...);

and no removing field or guarded request-handler queue sites. Its diff
only relocates the unchanged update_xdomain() function, so work can
still be queued after cancellation and run after the xdomain is freed.

So I think this is incomplete backport ?


thoughts ?


Thanks,
Harshit

>   static ssize_t key_show(struct device *dev, struct device_attribute *attr,
>   			char *buf)
>   {
> @@ -2480,19 +2493,6 @@ bool tb_xdomain_handle_request(struct tb
>   	return ret > 0;
>   }
>   
> -static int update_xdomain(struct device *dev, void *data)
> -{
> -	struct tb_xdomain *xd;
> -
> -	xd = tb_to_xdomain(dev);
> -	if (xd) {
> -		queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
> -				   msecs_to_jiffies(50));
> -	}
> -
> -	return 0;
> -}
> -
>   static void update_all_xdomains(void)
>   {
>   	bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
> 
> 
>