From: Sasha Levin Date: Tue, 4 Aug 2026 01:05:42 +0000 (-0400) Subject: Fixes for all trees X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c47877e7a018b93bb90b8a33f4c077a19999e941;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/series b/queue-5.10/series index f7bfe5c1ca..2e649fc059 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -199,3 +199,4 @@ hid-logitech-dj-prevent-report_id_dj_short-related-u.patch hid-logitech-dj-fix-wrong-detection-of-bad-dj_short-.patch net-qrtr-ns-limit-the-maximum-server-registration-pe.patch net-qrtr-ns-raise-node-count-limit-to-512.patch +tls-separate-no-async-decryption-request-handling-fr.patch diff --git a/queue-5.10/tls-separate-no-async-decryption-request-handling-fr.patch b/queue-5.10/tls-separate-no-async-decryption-request-handling-fr.patch new file mode 100644 index 0000000000..8444a3d1e6 --- /dev/null +++ b/queue-5.10/tls-separate-no-async-decryption-request-handling-fr.patch @@ -0,0 +1,64 @@ +From d2493b7bc220532b47092f75e897f66825443465 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 3 Aug 2026 12:51:49 -0700 +Subject: tls: separate no-async decryption request handling from async + +From: Sabrina Dubroca + +commit 41532b785e9d79636b3815a64ddf6a096647d011 upstream. + +If we're not doing async, the handling is much simpler. There's no +reference counting, we just need to wait for the completion to wake us +up and return its result. + +We should preferably also use a separate crypto_wait. I'm not seeing a +UAF as I did in the past, I think aec7961916f3 ("tls: fix race between +async notify and socket close") took care of it. + +This will make the next fix easier. + +Signed-off-by: Sabrina Dubroca +Link: https://lore.kernel.org/r/47bde5f649707610eaef9f0d679519966fc31061.1709132643.git.sd@queasysnail.net +Signed-off-by: Jakub Kicinski +Signed-off-by: Wengang Wang +Signed-off-by: Sasha Levin +--- + net/tls/tls_sw.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c +index 1732e3549a578..29650fa546664 100644 +--- a/net/tls/tls_sw.c ++++ b/net/tls/tls_sw.c +@@ -257,18 +257,21 @@ static int tls_do_decryption(struct sock *sk, + tls_decrypt_done, skb); + atomic_inc(&ctx->decrypt_pending); + } else { ++ DECLARE_CRYPTO_WAIT(wait); ++ + aead_request_set_callback(aead_req, + CRYPTO_TFM_REQ_MAY_BACKLOG, +- crypto_req_done, &ctx->async_wait); ++ crypto_req_done, &wait); ++ ++ ret = crypto_aead_decrypt(aead_req); ++ if (ret == -EINPROGRESS || ret == -EBUSY) ++ ret = crypto_wait_req(ret, &wait); ++ return ret; + } + + ret = crypto_aead_decrypt(aead_req); +- if (ret == -EINPROGRESS) { +- if (async) +- return ret; +- +- ret = crypto_wait_req(ret, &ctx->async_wait); +- } ++ if (ret == -EINPROGRESS) ++ return ret; + + if (async) + atomic_dec(&ctx->decrypt_pending); +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index 825d3fff9e..f2c8434b1c 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -242,3 +242,4 @@ hid-logitech-dj-prevent-report_id_dj_short-related-u.patch hid-logitech-dj-fix-wrong-detection-of-bad-dj_short-.patch net-qrtr-ns-limit-the-maximum-server-registration-pe.patch net-qrtr-ns-raise-node-count-limit-to-512.patch +tls-separate-no-async-decryption-request-handling-fr.patch diff --git a/queue-5.15/tls-separate-no-async-decryption-request-handling-fr.patch b/queue-5.15/tls-separate-no-async-decryption-request-handling-fr.patch new file mode 100644 index 0000000000..7c819046ec --- /dev/null +++ b/queue-5.15/tls-separate-no-async-decryption-request-handling-fr.patch @@ -0,0 +1,64 @@ +From 4899deec200708913c7ed2b8b658b54f49a68559 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 3 Aug 2026 12:32:16 -0700 +Subject: tls: separate no-async decryption request handling from async + +From: Sabrina Dubroca + +commit 41532b785e9d79636b3815a64ddf6a096647d011 upstream. + +If we're not doing async, the handling is much simpler. There's no +reference counting, we just need to wait for the completion to wake us +up and return its result. + +We should preferably also use a separate crypto_wait. I'm not seeing a +UAF as I did in the past, I think aec7961916f3 ("tls: fix race between +async notify and socket close") took care of it. + +This will make the next fix easier. + +Signed-off-by: Sabrina Dubroca +Link: https://lore.kernel.org/r/47bde5f649707610eaef9f0d679519966fc31061.1709132643.git.sd@queasysnail.net +Signed-off-by: Jakub Kicinski +Signed-off-by: Wengang Wang +Signed-off-by: Sasha Levin +--- + net/tls/tls_sw.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c +index 389597bd7ae52..0cdc9131aff22 100644 +--- a/net/tls/tls_sw.c ++++ b/net/tls/tls_sw.c +@@ -278,9 +278,15 @@ static int tls_do_decryption(struct sock *sk, + BUILD_BUG_ON_INVALID(atomic_read(&ctx->decrypt_pending) < 1); + atomic_inc(&ctx->decrypt_pending); + } else { ++ DECLARE_CRYPTO_WAIT(wait); ++ + aead_request_set_callback(aead_req, + CRYPTO_TFM_REQ_MAY_BACKLOG, +- crypto_req_done, &ctx->async_wait); ++ crypto_req_done, &wait); ++ ret = crypto_aead_decrypt(aead_req); ++ if (ret == -EINPROGRESS || ret == -EBUSY) ++ ret = crypto_wait_req(ret, &wait); ++ return ret; + } + + ret = crypto_aead_decrypt(aead_req); +@@ -289,10 +295,7 @@ static int tls_do_decryption(struct sock *sk, + ret = ret ?: -EINPROGRESS; + } + if (ret == -EINPROGRESS) { +- if (darg->async) +- return 0; +- +- ret = crypto_wait_req(ret, &ctx->async_wait); ++ return 0; + } else if (darg->async) { + atomic_dec(&ctx->decrypt_pending); + } +-- +2.53.0 + diff --git a/queue-6.12/netconsole-avoid-oob-reads-msg-is-not-nul-terminated.patch b/queue-6.12/netconsole-avoid-oob-reads-msg-is-not-nul-terminated.patch new file mode 100644 index 0000000000..9ba1fb11be --- /dev/null +++ b/queue-6.12/netconsole-avoid-oob-reads-msg-is-not-nul-terminated.patch @@ -0,0 +1,69 @@ +From e4f30b1090d69bbe3ec6c12479f5277fafa093bd Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 3 Aug 2026 20:41:29 +0200 +Subject: netconsole: avoid OOB reads, msg is not nul-terminated + +From: Jakub Kicinski + +[ Upstream commit 82aec772fca2223bc5774bd9af486fd95766e578 ] + +msg passed to netconsole from the console subsystem is not guaranteed +to be nul-terminated. Before recent +commit 7eab73b18630 ("netconsole: convert to NBCON console infrastructure") +the message would be placed in printk_shared_pbufs, a static global +buffer, so KASAN had harder time catching OOB accesses. Now we see: + + printk: console [netcon_ext0] enabled + BUG: KASAN: slab-out-of-bounds in string+0x1f7/0x240 + Read of size 1 at addr ffff88813b6d4c00 by task pr/netcon_ext0/594 + + CPU: 65 UID: 0 PID: 594 Comm: pr/netcon_ext0 Not tainted 6.19.0-11754-g4246fd6547c9 + Call Trace: + kasan_report+0xe4/0x120 + string+0x1f7/0x240 + vsnprintf+0x655/0xba0 + scnprintf+0xba/0x120 + netconsole_write+0x3fe/0xa10 + nbcon_emit_next_record+0x46e/0x860 + nbcon_kthread_func+0x623/0x750 + + Allocated by task 1: + nbcon_alloc+0x1ea/0x450 + register_console+0x26b/0xe10 + init_netconsole+0xbb0/0xda0 + + The buggy address belongs to the object at ffff88813b6d4000 + which belongs to the cache kmalloc-4k of size 4096 + The buggy address is located 0 bytes to the right of + allocated 3072-byte region [ffff88813b6d4000, ffff88813b6d4c00) + +Fixes: c62c0a17f9b7 ("netconsole: Append kernel version to message") +Signed-off-by: Jakub Kicinski +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20260219195021.2099699-1-kuba@kernel.org +Signed-off-by: Paolo Abeni +[ mb: adjusted context; in 6.12 the affected code lives in + send_ext_msg_udp() and writes to a static buffer instead of nt->buf ] +Signed-off-by: Markus Boehme +Signed-off-by: Sasha Levin +--- + drivers/net/netconsole.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c +index 60375bb814a1e..3b76ec3cd49b8 100644 +--- a/drivers/net/netconsole.c ++++ b/drivers/net/netconsole.c +@@ -1103,7 +1103,8 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg, + if (msg_len + release_len + userdata_len <= MAX_PRINT_CHUNK) { + /* No fragmentation needed */ + if (nt->release) { +- scnprintf(buf, MAX_PRINT_CHUNK, "%s,%s", release, msg); ++ scnprintf(buf, MAX_PRINT_CHUNK, "%s,%.*s", release, ++ msg_len, msg); + msg_len += release_len; + } else { + memcpy(buf, msg, msg_len); +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index 4065f68f20..85365a67ae 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -15,3 +15,5 @@ hid-logitech-dj-standardise-hid_report_enum-variable.patch hid-logitech-dj-prevent-report_id_dj_short-related-u.patch hid-logitech-dj-fix-wrong-detection-of-bad-dj_short-.patch bpf-reset-register-bounds-before-narrowing-retval-ra.patch +netconsole-avoid-oob-reads-msg-is-not-nul-terminated.patch +thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch diff --git a/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch b/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch new file mode 100644 index 0000000000..b94a098b50 --- /dev/null +++ b/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch @@ -0,0 +1,149 @@ +From f9c73585c00541f23f471d5f77c63da722bd1f29 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 27 May 2026 07:46:04 -0400 +Subject: thunderbolt: Prevent XDomain delayed work use-after-free on + disconnect + +From: Michael Bommarito + +[ 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(). 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: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +--- + drivers/thunderbolt/xdomain.c | 40 ++++++++++++++++++++++++++--------- + include/linux/thunderbolt.h | 3 +++ + 2 files changed, 33 insertions(+), 10 deletions(-) + +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +index 06357073f4ab8..ffe7ce0737491 100644 +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -783,9 +783,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; + + case UUID_REQUEST_OLD: +@@ -798,8 +802,12 @@ static void tb_xdp_handle_request(struct work_struct *work) + * received UUID request from the remote host. + */ + if (!ret && xd && xd->state == 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; + +@@ -827,9 +835,13 @@ static void tb_xdp_handle_request(struct work_struct *work) + + ret = tb_xdp_link_state_change_response(ctl, route, + sequence, 0); +- xd->target_link_width = 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 = 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); +@@ -2068,6 +2080,10 @@ void tb_xdomain_remove(struct tb_xdomain *xd) + { + tb_xdomain_debugfs_remove(xd); + ++ mutex_lock(&xd->lock); ++ xd->removing = true; ++ mutex_unlock(&xd->lock); ++ + stop_handshake(xd); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); +@@ -2464,8 +2480,12 @@ static int update_xdomain(struct device *dev, void *data) + + xd = 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); + } + + return 0; +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 7d902d8c054b2..4ef9c43026569 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -202,6 +202,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 +@@ -250,6 +252,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; +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index f199159411..aca90a1fee 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -10,3 +10,4 @@ hid-logitech-dj-prevent-report_id_dj_short-related-u.patch hid-logitech-dj-fix-wrong-detection-of-bad-dj_short-.patch lib-alloc_tag-introduce-mem_alloc_profiling_permanen.patch mm-slab-prevent-unbounded-recursion-in-free-path-wit.patch +thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch diff --git a/queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch b/queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch new file mode 100644 index 0000000000..f7bb022615 --- /dev/null +++ b/queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch @@ -0,0 +1,149 @@ +From d30a3d044ad5ab0b81f233be983ae3e300ec9e8c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 27 May 2026 07:46:04 -0400 +Subject: thunderbolt: Prevent XDomain delayed work use-after-free on + disconnect + +From: Michael Bommarito + +[ 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(). 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: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +--- + drivers/thunderbolt/xdomain.c | 40 ++++++++++++++++++++++++++--------- + include/linux/thunderbolt.h | 3 +++ + 2 files changed, 33 insertions(+), 10 deletions(-) + +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +index 458476907eab0..6b16fc7254b8e 100644 +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -785,9 +785,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; + + case UUID_REQUEST_OLD: +@@ -800,8 +804,12 @@ static void tb_xdp_handle_request(struct work_struct *work) + * received UUID request from the remote host. + */ + if (!ret && xd && xd->state == 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; + +@@ -829,9 +837,13 @@ static void tb_xdp_handle_request(struct work_struct *work) + + ret = tb_xdp_link_state_change_response(ctl, route, + sequence, 0); +- xd->target_link_width = 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 = 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); +@@ -2074,6 +2086,10 @@ void tb_xdomain_remove(struct tb_xdomain *xd) + { + tb_xdomain_debugfs_remove(xd); + ++ mutex_lock(&xd->lock); ++ xd->removing = true; ++ mutex_unlock(&xd->lock); ++ + stop_handshake(xd); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); +@@ -2484,8 +2500,12 @@ static int update_xdomain(struct device *dev, void *data) + + xd = 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); + } + + return 0; +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 0ba112175bb39..7204586c10c3e 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -209,6 +209,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 +@@ -257,6 +259,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; +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 6542c4811d..441fddf862 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -6,3 +6,4 @@ hid-logitech-dj-standardise-hid_report_enum-variable.patch hid-logitech-dj-prevent-report_id_dj_short-related-u.patch hid-logitech-dj-fix-wrong-detection-of-bad-dj_short-.patch soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch +thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch diff --git a/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch b/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch new file mode 100644 index 0000000000..5560bd59f8 --- /dev/null +++ b/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch @@ -0,0 +1,149 @@ +From 1549dc04bc61ac157e5a782b18d6fb0db62e471f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 27 May 2026 07:46:04 -0400 +Subject: thunderbolt: Prevent XDomain delayed work use-after-free on + disconnect + +From: Michael Bommarito + +[ 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(). 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: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +--- + drivers/thunderbolt/xdomain.c | 40 ++++++++++++++++++++++++++--------- + include/linux/thunderbolt.h | 3 +++ + 2 files changed, 33 insertions(+), 10 deletions(-) + +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +index a5ec70327d81a..87fbced665b91 100644 +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -783,9 +783,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; + + case UUID_REQUEST_OLD: +@@ -798,8 +802,12 @@ static void tb_xdp_handle_request(struct work_struct *work) + * received UUID request from the remote host. + */ + if (!ret && xd && xd->state == 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; + +@@ -827,9 +835,13 @@ static void tb_xdp_handle_request(struct work_struct *work) + + ret = tb_xdp_link_state_change_response(ctl, route, + sequence, 0); +- xd->target_link_width = 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 = 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); +@@ -2018,6 +2030,10 @@ void tb_xdomain_remove(struct tb_xdomain *xd) + { + tb_xdomain_debugfs_remove(xd); + ++ mutex_lock(&xd->lock); ++ xd->removing = true; ++ mutex_unlock(&xd->lock); ++ + stop_handshake(xd); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); +@@ -2412,8 +2428,12 @@ static int update_xdomain(struct device *dev, void *data) + + xd = 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); + } + + return 0; +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 02333f47c9941..d95e69c1ac63b 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -203,6 +203,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 +@@ -251,6 +253,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; +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index 519f7143ad..c1b4db8867 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -3,3 +3,4 @@ mm-slab-decouple-slab_no_sheaves-from-slab_no_obj_ex.patch lib-alloc_tag-introduce-mem_alloc_profiling_permanen.patch mm-slab-prevent-unbounded-recursion-in-free-path-wit.patch alsa-hda-realtek-add-quirk-for-hp-dragonfly-folio-g3.patch +thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch diff --git a/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch b/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch new file mode 100644 index 0000000000..9021438fa6 --- /dev/null +++ b/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-f.patch @@ -0,0 +1,149 @@ +From 061e86c29d697bc254a2975f52ad41d955044b4f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 27 May 2026 07:46:04 -0400 +Subject: thunderbolt: Prevent XDomain delayed work use-after-free on + disconnect + +From: Michael Bommarito + +[ 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(). 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: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +--- + drivers/thunderbolt/xdomain.c | 40 ++++++++++++++++++++++++++--------- + include/linux/thunderbolt.h | 3 +++ + 2 files changed, 33 insertions(+), 10 deletions(-) + +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +index 1fd1cf4295a2a..55f91ead8f13b 100644 +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -785,9 +785,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; + + case UUID_REQUEST_OLD: +@@ -800,8 +804,12 @@ static void tb_xdp_handle_request(struct work_struct *work) + * received UUID request from the remote host. + */ + if (!ret && xd && xd->state == 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; + +@@ -829,9 +837,13 @@ static void tb_xdp_handle_request(struct work_struct *work) + + ret = tb_xdp_link_state_change_response(ctl, route, + sequence, 0); +- xd->target_link_width = 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 = 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); +@@ -2074,6 +2086,10 @@ void tb_xdomain_remove(struct tb_xdomain *xd) + { + tb_xdomain_debugfs_remove(xd); + ++ mutex_lock(&xd->lock); ++ xd->removing = true; ++ mutex_unlock(&xd->lock); ++ + stop_handshake(xd); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); +@@ -2484,8 +2500,12 @@ static int update_xdomain(struct device *dev, void *data) + + xd = 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); + } + + return 0; +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 0ba112175bb39..7204586c10c3e 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -209,6 +209,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 +@@ -257,6 +259,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; +-- +2.53.0 +