From nobody Mon Jun 8 18:55:57 2026 Received: from mail-qv1-f44.google.com (mail-qv1-f44.google.com [209.85.219.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7B073400E10 for ; Wed, 27 May 2026 11:46:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779882384; cv=none; b=c4uCBkBXZjB1v24jjARjnjK9IlQgs34KRbldS7n1ppu3raeRiw4OqUX4Y5qLkCba+phJuf50kf5DseL+sJ/rXBc5cNVeziJW7iGC2LhpXhTwqat56wgc1ViEnPMvMHjueH4rizP4FGGtxS2J25Hi8s6Pt5PQUBYpuLvNxSUBvdc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779882384; c=relaxed/simple; bh=g3K3kjzo4h4Yw4PI3ZebZ1RdrnLML8MPT0Kp1gm7o8k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=I+UmWoI6hE/Gx3lsy43Sa82lSTU+W44B8pGSd/HazxBnBXT3fuvkX/dx/IIzepT2hTJsAS1vwRMC13Z43sFug//xQkrAwJtnTyLYARJ0nwIewoZRkN5K1OAxLwUgGP/V0AV/KEduhCei0N2tBaJTKtoYgvb7sXyXyXcttDit7TQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JWOfGpm8; arc=none smtp.client-ip=209.85.219.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JWOfGpm8" Received: by mail-qv1-f44.google.com with SMTP id 6a1803df08f44-8b701756684so130678786d6.1 for ; Wed, 27 May 2026 04:46:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779882381; x=1780487181; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=irXQtbhRBkvp9yH1UkJ164KDCO7pyrFnYr4plfnFx/E=; b=JWOfGpm8ytu58drOsdYRRZIO6HET2mcbRd3G5T5aIbs1ESdFpZ5dyvmJlaWHCdYZRv WsWjOLCgO6GgJEaWmAM+svQRqc5wR7NHheiXpZB2/MjoxKRn9FoVEt5wLBTm2XsZm/kd /HHAS9D1J9TMYawYT7B8MxRv0eJ7Yhm8g/F3UwURdwYKK4wVJhUiBQHsU7Bo3a7Rets6 2u9y+Fd8gWAPDS51wkT42TJhv+GMd4DRCsNZgZaK6q+R3+RuRvIyMTBfDIX0A6AglDL6 /RzkEUCw0pi4tpTw79RAWMuZ74V6qGe9rgNc7aD02nLPu/Bb294y+ywGG2O9TcLxxKh+ Txrg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779882381; x=1780487181; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=irXQtbhRBkvp9yH1UkJ164KDCO7pyrFnYr4plfnFx/E=; b=UNEMNPLq+SKVKcrJfsbdcSNJPMD14nPly37FpK/0sPs+8vuW346IBfDPMgN+0sntx0 Ez8UuY5Zzfbg828aUheP+w8gBD+WgMB3SkHSV0gIOfta2X1bAr4FDXhFu4RF6B4t4iIx 9Au4T845AUO+x5WPTysmFVyNCbexf2qM0MSmSxrQWWLvd4SMZ7IKOpYAJ3VWsjWm6+Xh d4E8F4ZNKhm9JIxDYNb56PK+Y6LF6MCadcWyj0VwZM+6EakGNT5F7US5B+5qwhnImN9N /wtSGwGyQQm7nZXOHcFj7QcIOYnHHwnvL/9eqh/wagjQ449GsJqQ45uQ8rjle72v19Ue 5t9A== X-Forwarded-Encrypted: i=1; AFNElJ/mSwHjkO+7H0YnEB1b8TbBRz70SmS2d3DeDMdijL1o93p8nrPUO6WmDY+cO2uJEXneMbBh5h1NSZBehZU=@vger.kernel.org X-Gm-Message-State: AOJu0Ywye6h/KNi1POt88Cw8/8IdKt+R9voR/d7kfXVxmPZL/+PWE49/ Ubjn+G4UzPExaopvXEf43cfr485bDtYB81HExc8DQjxAWnfcyNcSm5GR X-Gm-Gg: Acq92OGdvDgYpsk5wQCWckPOexsrQwcQsASQjC8ZJoGq8b4mOZ1IoBjpdDtJUu8BbqX SV+awaI+RnYjb8BF3Sac7vC2fgExA7QErJqUATpgNCghM8sY+am6qiJ5jCDM88/wTgKb9+AlJ5j Z/2aeuKRWYP36itNYVpvDpJX9SjPaNCVPtZGUphgLZZKBelXIL8n2a3Iwg1PEpjFdWjTtMaU8co XLsswlRG9E+wqtvTN+WcYFtsHpAsI91qxYn52unX2z8wnhLvRGjdr34uUstT0K1dWluw7AbV4qG cbUVhChiCGaNyOi6lzSlfePXtqO/N18Yo+IRRhNqTVrrxSPJ/1RpvK4PMz4QTD5IvIunfxQjO9f oHdzeor/FYWqXm13nOjdsGsqORFsMtSrPlA5WPoHZx99i1tE8GN+8dEu0AjpmwlwC+z6aAg92pp X2l2YA6qpos2Vmq2D21UWkaFyBSxhDsRNsScGe3bl+bVm2FEgXnlTvZJcSYiIO8IQ0jUWr7LWfr LqPCChtzhibGj0b4f5X5Eagk+1L0GR67M8hCTe72KQuiFA7bh4qQw== X-Received: by 2002:a05:6214:5403:b0:8cb:e6bf:d473 with SMTP id 6a1803df08f44-8cc6e708327mr358801106d6.35.1779882381376; Wed, 27 May 2026 04:46:21 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8cc8132c625sm163933166d6.48.2026.05.27.04.46.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 27 May 2026 04:46:20 -0700 (PDT) From: Michael Bommarito To: Mika Westerberg , Andreas Noever , Yehezkel Bernat Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2] thunderbolt: prevent XDomain delayed work use-after-free on disconnect Date: Wed, 27 May 2026 07:46:04 -0400 Message-ID: <20260527114604.1197561-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260525125736.1268929-1-michael.bommarito@gmail.com> References: <20260525125736.1268929-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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(). 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 =3D=3D true and skips the queue. Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system wo= rkqueue") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito --- v2: Rebased onto thunderbolt.git/next per Mika's request. Verified the race persists on next: tb_xdp_handle_request still runs on system_wq, the remove/unregister split does not add synchronization with the queue sites. Updated commit message to reflect that tb_xdomain_unregister() now does the device_unregister (split from tb_xdomain_remove on next). drivers/thunderbolt/xdomain.c | 41 ++++++++++++++++++++++++++--------- include/linux/thunderbolt.h | 3 +++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 781d88d06b935..fe6c5ac703f4d 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -803,9 +803,13 @@ static void tb_xdp_handle_request(struct work_struct *= work) * the xdomain related to this connection as well in * case there is a change in services it offers. */ - if (xd && device_is_registered(&xd->dev)) - queue_delayed_work(tb->wq, &xd->state_work, - msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + if (xd) { + mutex_lock(&xd->lock); + if (!xd->removing && device_is_registered(&xd->dev)) + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + mutex_unlock(&xd->lock); + } break; =20 case UUID_REQUEST_OLD: @@ -818,8 +822,12 @@ static void tb_xdp_handle_request(struct work_struct *= work) * received UUID request from the remote host. */ if (!ret && xd && xd->state =3D=3D XDOMAIN_STATE_ERROR) { - dev_dbg(&xd->dev, "restarting handshake\n"); - start_handshake(xd); + mutex_lock(&xd->lock); + if (!xd->removing) { + dev_dbg(&xd->dev, "restarting handshake\n"); + start_handshake(xd); + } + mutex_unlock(&xd->lock); } break; =20 @@ -885,9 +893,13 @@ static void tb_xdp_handle_request(struct work_struct *= work) =20 ret =3D tb_xdp_link_state_change_response(ctl, route, sequence, 0); - xd->target_link_width =3D lsc->tlw; - queue_delayed_work(tb->wq, &xd->state_work, - msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + mutex_lock(&xd->lock); + if (!xd->removing) { + xd->target_link_width =3D lsc->tlw; + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + } + mutex_unlock(&xd->lock); } else { tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_READY); @@ -971,8 +983,12 @@ static int update_xdomain(struct device *dev, void *da= ta) =20 xd =3D tb_to_xdomain(dev); if (xd) { - queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, - msecs_to_jiffies(50)); + mutex_lock(&xd->lock); + if (!xd->removing) + queue_delayed_work(xd->tb->wq, + &xd->properties_changed_work, + msecs_to_jiffies(50)); + mutex_unlock(&xd->lock); } =20 return 0; @@ -2200,6 +2216,11 @@ static int unregister_service(struct device *dev, vo= id *data) void tb_xdomain_remove(struct tb_xdomain *xd) { tb_xdomain_debugfs_remove(xd); + + mutex_lock(&xd->lock); + xd->removing =3D true; + mutex_unlock(&xd->lock); + stop_handshake(xd); tb_xdomain_link_exit(xd); =20 diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index b5659f8835171..feb1af175cfde 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -213,6 +213,8 @@ enum tb_link_width { * @link_width: Width of the downstream facing link * @link_usb4: Downstream link is USB4 * @is_unplugged: The XDomain is unplugged + * @removing: Set by tb_xdomain_remove() under @lock to prevent + * concurrent delayed work queueing * @needs_uuid: If the XDomain does not have @remote_uuid it will be * queried first * @service_ids: Used to generate IDs for the services @@ -262,6 +264,7 @@ struct tb_xdomain { enum tb_link_width link_width; bool link_usb4; bool is_unplugged; + bool removing; bool needs_uuid; struct ida service_ids; struct ida in_hopids; base-commit: d73a08958e66849ea713d2f458b2fcf7b183f987 --=20 2.53.0