Netdev List
 help / color / mirror / Atom feed
* [PATCH 0/1] sctp: fix timer locking across association migration
@ 2026-11-10 12:02 liuyong
  2026-11-10 12:02 ` [PATCH 1/1] sctp: revalidate the association socket in timer callbacks liuyong
  0 siblings, 1 reply; 6+ messages in thread
From: liuyong @ 2026-11-10 12:02 UTC (permalink / raw)
  To: Marcelo Ricardo Leitner, Xin Long; +Cc: linux-sctp, netdev, linux-kernel

This patch makes SCTP timer callbacks revalidate the association's socket
after taking its BH lock. If accept or peeloff moves the association before
the callback acquires the lock, the callback can otherwise process queues
under the old socket's lock while abortive close tears them down under the
new socket's lock. The T3 retransmit path then reads a freed chunk.

One possible interleaving on the unfixed kernel (time flows downward):

  CPU 0: sctp_generate_t3_rtx_event()  CPU 1 (peeloff / abortive close)
  -----------------------------------  ------------------------------------
  sk = asoc->base.sk; /* oldsk */
                                       lock_sock(oldsk);
                                       lock_sock_nested(newsk, ...);
                                       asoc->base.sk = newsk;
                                       release_sock(newsk);
                                       release_sock(oldsk);
  bh_lock_sock(sk); /* oldsk */
  sock_owned_by_user(sk) == false
  sctp_retransmit_mark(...);
  chunk = list_entry(...);
                                       lock_sock(newsk);
                                       sctp_primitive_ABORT(...);
                                       sctp_association_free(asoc);
                                       sctp_outq_free(&asoc->outqueue);
                                       __sctp_outq_teardown(...);
                                       /* chunk is freed */
  sctp_chunk_abandoned(chunk);
  /* reads freed chunk: UAF */
  bh_unlock_sock(sk);

The development and reproduction baseline is Linux 7.4.0-rc2 at commit
b3dfbde62cf1981c4638939c159dfd087b7a72e9. The UAF was reproduced with an
mdelay() instrumentation patch widening the windows between the T3
callback caching the socket and locking it, and between the retransmit
path obtaining a queued chunk and using it. With unacknowledged
DATA pending, peeloff moves the association to a new socket; abortive close
of that socket frees the chunks while the timer is still running.

The patch treats an association/socket mismatch like a busy socket and
reschedules the timer through the existing reference-counted retry path.
The next callback reads the current socket again. The check is applied to
the six timer entry points with this locking pattern.

KASAN report:

[  417.538580] ==================================================================
[  417.538597] BUG: KASAN: slab-use-after-free in sctp_chunk_abandoned+0x706/0x840
[  417.538674] Read of size 8 at addr ffff8881079c8a90 by task swapper/0/0

[  417.538684] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.4.0-rc2-gb3dfbde62cf1-dirty #2 PREEMPT(lazy) 
[  417.538687] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[  417.538691] Call Trace:
[  417.538698]  <IRQ>
[  417.538700]  dump_stack_lvl+0x53/0x70
[  417.538710]  print_report+0xd0/0x630
[  417.538737]  kasan_report+0xce/0x100
[  417.538741]  sctp_chunk_abandoned+0x706/0x840
[  417.538746]  sctp_retransmit_mark+0x107/0xb00
[  417.538749]  sctp_retransmit+0x25a/0x5b0
[  417.538751]  sctp_do_sm+0x3c01/0x5c30
[  417.538810]  sctp_generate_t3_rtx_event+0x174/0x270
[  417.538814]  call_timer_fn+0x29/0x230
[  417.538818]  __run_timers+0x56a/0x8d0
[  417.538861]  timer_expire_remote+0xd3/0x120
[  417.538872]  tmigr_handle_remote_up+0x4e0/0xa60
[  417.538890]  __walk_groups_from+0x40/0x150
[  417.538892]  tmigr_handle_remote+0x229/0x2c0
[  417.538901]  run_timer_softirq+0x1f5/0x250
[  417.538908]  handle_softirqs+0x1b3/0x600
[  417.538917]  __irq_exit_rcu+0xfc/0x160
[  417.538919]  sysvec_apic_timer_interrupt+0x6b/0x80
[  417.538923]  </IRQ>
[  417.538924]  <TASK>
[  417.538925]  asm_sysvec_apic_timer_interrupt+0x1a/0x20
[  417.538928] RIP: 0010:pv_native_safe_halt+0xf/0x20
[  417.538931] Code: 44 de 00 c3 cc cc cc cc 0f 1f 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa eb 07 0f 00 2d 73 44 16 00 fb f4 <c3> cc cc cc cc 66 2e 0f 1f 84 00 00 00 00 00 66 90 90 90 90 90 90
[  417.538933] RSP: 0018:ffffffff85e07d10 EFLAGS: 00000212
[  417.538939] RAX: ffff88819372e000 RBX: ffffffff85e18700 RCX: ffffffff84ef0c0b
[  417.538940] RDX: ffffed10236062a3 RSI: 0000000000000004 RDI: 00000000000324bc
[  417.538942] RBP: fffffbfff0bc30e0 R08: 0000000000000001 R09: ffffed10236062a2
[  417.538943] R10: ffff88811b031513 R11: 0000000000000002 R12: 0000000000000000
[  417.538944] R13: 0000000000000000 R14: 1ffffffff0bc0fa6 R15: dffffc0000000000
[  417.538948]  default_idle+0x9/0x10
[  417.538952]  default_idle_call+0x39/0x60
[  417.538954]  do_idle+0x37b/0x560
[  417.538960]  cpu_startup_entry+0x4f/0x60
[  417.538962]  rest_init+0x186/0x1b0
[  417.538997]  start_kernel+0x354/0x3b0
[  417.539009]  x86_64_start_reservations+0x18/0x30
[  417.539013]  x86_64_start_kernel+0x111/0x120
[  417.539015]  common_startup_64+0x13e/0x158
[  417.539023]  </TASK>

[  417.539024] Allocated by task 255:
[  417.539027]  kasan_save_stack+0x33/0x60
[  417.539031]  kasan_save_track+0x14/0x30
[  417.539032]  __kasan_slab_alloc+0x6e/0x70
[  417.539034]  kmem_cache_alloc_noprof+0x10e/0x340
[  417.539039]  sctp_chunkify+0x42/0x270
[  417.539041]  _sctp_make_chunk+0x11c/0x230
[  417.539042]  sctp_make_datafrag_empty+0x134/0x200
[  417.539044]  sctp_datamsg_from_user+0x49d/0xf70
[  417.539046]  sctp_sendmsg_to_asoc+0x6cb/0x16b0
[  417.539049]  sctp_sendmsg+0x5c8/0x1960
[  417.539051]  __sys_sendto+0x31c/0x390
[  417.539061]  __x64_sys_sendto+0xdb/0x1b0
[  417.539063]  do_syscall_64+0xdf/0x4c0
[  417.539065]  entry_SYSCALL_64_after_hwframe+0x77/0x7f

[  417.539068] Freed by task 255:
[  417.539069]  kasan_save_stack+0x33/0x60
[  417.539070]  kasan_save_track+0x14/0x30
[  417.539072]  kasan_save_free_info+0x3b/0x60
[  417.539075]  __kasan_slab_free+0x43/0x70
[  417.539077]  kmem_cache_free+0xba/0x3a0
[  417.539078]  __sctp_outq_teardown+0x16c/0x960
[  417.539080]  sctp_association_free+0x157/0x5e0
[  417.539082]  sctp_do_sm+0x31c7/0x5c30
[  417.539085]  sctp_primitive_ABORT+0x7c/0xd0
[  417.539086]  sctp_close+0x203/0x810
[  417.539088]  inet_release+0xc9/0x1c0
[  417.539096]  __sock_release+0xa1/0x260
[  417.539098]  sock_close+0x10/0x20
[  417.539100]  __fput+0x2f8/0x9e0
[  417.539107]  fput_close_sync+0xe2/0x190
[  417.539109]  __x64_sys_close+0x78/0xd0
[  417.539111]  do_syscall_64+0xdf/0x4c0
[  417.539113]  entry_SYSCALL_64_after_hwframe+0x77/0x7f

[  417.539115] The buggy address belongs to the object at ffff8881079c8a00
                which belongs to the cache sctp_chunk of size 256
[  417.539117] The buggy address is located 144 bytes inside of
                freed 256-byte region [ffff8881079c8a00, ffff8881079c8b00)

[  417.539120] The buggy address belongs to the physical page:
[  417.539122] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1079c8
[  417.539125] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[  417.539126] flags: 0x200000000000040(head|node=0|zone=2)
[  417.539132] page_type: f5(slab)
[  417.539134] raw: 0200000000000040 ffff8881017d5040 dead000000000100 dead000000000122
[  417.539139] raw: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[  417.539140] head: 0200000000000040 ffff8881017d5040 dead000000000100 dead000000000122
[  417.539142] head: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[  417.539143] head: 0200000000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
[  417.539145] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[  417.539146] page dumped because: kasan: bad access detected

[  417.539147] Memory state around the buggy address:
[  417.539148]  ffff8881079c8980: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  417.539149]  ffff8881079c8a00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  417.539150] >ffff8881079c8a80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  417.539151]                          ^
[  417.539152]  ffff8881079c8b00: fc fc fc fc fc fc fc fc fa fb fb fb fb fb fb fb
[  417.539153]  ffff8881079c8b80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  417.539154] ==================================================================
[  417.539179] Disabling lock debugging due to kernel taint

The issue was discovered and fixed by Qihoo 360's Tulongfeng multi-agent system.

A reproducer and additional reproduction materials are available. Please
contact me privately if you need them.

Regards,
liuyong
360 Vulnerability Research Institute

liuyong (1):
  sctp: revalidate the association socket in timer callbacks

 net/sctp/associola.c     |  4 ++--
 net/sctp/sm_sideeffect.c | 41 ++++++++++++++++++++--------------------
 2 files changed, 22 insertions(+), 23 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/1] sctp: revalidate the association socket in timer callbacks
  2026-11-10 12:02 [PATCH 0/1] sctp: fix timer locking across association migration liuyong
@ 2026-11-10 12:02 ` liuyong
  2026-11-12 18:22   ` Jakub Kicinski
                     ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: liuyong @ 2026-11-10 12:02 UTC (permalink / raw)
  To: Marcelo Ricardo Leitner, Xin Long; +Cc: linux-sctp, netdev, linux-kernel

SCTP timer callbacks cache asoc->base.sk before acquiring its BH lock.
An accept or peeloff can move the association to a new socket in between.
The callback can then run the state machine under the old socket lock,
while an abort on the new socket tears down the association's queues.
The timer's transport and association references do not keep those
queues alive. With delays inserted to widen the race, KASAN reported a
slab-use-after-free in sctp_chunk_abandoned() from the T3 retransmit path.

Recheck the association's socket after acquiring its BH lock. Treat a
mismatch like a busy socket and use the existing reference-counted timer
rescheduling path. The next callback reads the current socket again,
preventing state-machine execution under the old socket's lock. Retain
the user-ownership check to handle a migration already in progress.

Apply the check to the T3, association, heartbeat, protocol-unreachable,
reconfiguration and probe timers. Their BH context and the endpoint's
RCU-deferred sock_put() protect the cached socket during the callback.
Pair the socket pointer reads with WRITE_ONCE() in sctp_assoc_migrate(),
taking the new socket reference before updating the pointer.

The issue was discovered and fixed by Qihoo 360's Tulongfeng multi-agent
system.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: liuyong <pkfxxxing@gmail.com>
---
 net/sctp/associola.c     |  4 ++--
 net/sctp/sm_sideeffect.c | 41 ++++++++++++++++++++--------------------
 2 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/net/sctp/associola.c b/net/sctp/associola.c
index 4521be3bd8..c5d520c8d7 100644
--- a/net/sctp/associola.c
+++ b/net/sctp/associola.c
@@ -1088,8 +1088,8 @@ void sctp_assoc_migrate(struct sctp_association *assoc, struct sock *newsk)
 	sctp_endpoint_hold(assoc->ep);
 
 	/* Get a reference to the new sock.  */
-	assoc->base.sk = newsk;
-	sock_hold(assoc->base.sk);
+	sock_hold(newsk);
+	WRITE_ONCE(assoc->base.sk, newsk);
 
 	/* Add the association to the new endpoint's list of associations.  */
 	sctp_endpoint_add_asoc(newsp->ep, assoc);
diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c
index 35f540fb15..bd6b23369f 100644
--- a/net/sctp/sm_sideeffect.c
+++ b/net/sctp/sm_sideeffect.c
@@ -233,15 +233,14 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, T3_rtx_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error;
 
-	/* Check whether a task is in the sock.  */
-
+	/* Defer if the socket is busy or the association has migrated. */
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -271,13 +270,13 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
 static void sctp_generate_timeout_event(struct sctp_association *asoc,
 					enum sctp_event_timeout timeout_type)
 {
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy: timer %d\n", __func__,
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved: timer %d\n", __func__,
 			 timeout_type);
 
 		/* Try again later.  */
@@ -362,7 +361,7 @@ static void sctp_generate_autoclose_event(struct timer_list *t)
 	sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_AUTOCLOSE);
 }
 
-/* Generate a heart beat event.  If the sock is busy, reschedule.   Make
+/* Generate a heart beat event.  If the sock is busy or moved, reschedule.   Make
  * sure that the transport is still valid.
  */
 void sctp_generate_heartbeat_event(struct timer_list *t)
@@ -370,14 +369,14 @@ void sctp_generate_heartbeat_event(struct timer_list *t)
 	struct sctp_transport *transport = timer_container_of(transport, t,
 							      hb_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	u32 elapsed, timeout;
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -418,12 +417,12 @@ void sctp_generate_proto_unreach_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, proto_unreach_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -454,13 +453,13 @@ void sctp_generate_reconf_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, reconf_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -492,13 +491,13 @@ void sctp_generate_probe_event(struct timer_list *t)
 	struct sctp_transport *transport = timer_container_of(transport, t,
 							      probe_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] sctp: revalidate the association socket in timer callbacks
  2026-11-10 12:02 ` [PATCH 1/1] sctp: revalidate the association socket in timer callbacks liuyong
@ 2026-11-12 18:22   ` Jakub Kicinski
  2026-11-12 21:13   ` Xin Long
  2026-11-13  3:28   ` [PATCH net v2] sctp: revalidate association sockets after locking Yong Liu
  2 siblings, 0 replies; 6+ messages in thread
From: Jakub Kicinski @ 2026-11-12 18:22 UTC (permalink / raw)
  To: liuyong; +Cc: Marcelo Ricardo Leitner, Xin Long, linux-sctp, netdev,
	linux-kernel

On Tue, 10 Nov 2026 20:02:09 +0800 liuyong wrote:
> Signed-off-by: liuyong <pkfxxxing@gmail.com>

We need real/legal names, one lowercase string doesn't look right.
Please update
-- 
pw-bot: cr

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] sctp: revalidate the association socket in timer callbacks
  2026-11-10 12:02 ` [PATCH 1/1] sctp: revalidate the association socket in timer callbacks liuyong
  2026-11-12 18:22   ` Jakub Kicinski
@ 2026-11-12 21:13   ` Xin Long
  2026-11-13  3:28   ` [PATCH net v2] sctp: revalidate association sockets after locking Yong Liu
  2 siblings, 0 replies; 6+ messages in thread
From: Xin Long @ 2026-11-12 21:13 UTC (permalink / raw)
  To: liuyong; +Cc: Marcelo Ricardo Leitner, linux-sctp, netdev, linux-kernel

On Tue, Nov 10, 2026 at 7:03 AM liuyong <pkfxxxing@gmail.com> wrote:
>
> SCTP timer callbacks cache asoc->base.sk before acquiring its BH lock.
> An accept or peeloff can move the association to a new socket in between.
> The callback can then run the state machine under the old socket lock,
> while an abort on the new socket tears down the association's queues.
> The timer's transport and association references do not keep those
> queues alive. With delays inserted to widen the race, KASAN reported a
> slab-use-after-free in sctp_chunk_abandoned() from the T3 retransmit path.
>
> Recheck the association's socket after acquiring its BH lock. Treat a
> mismatch like a busy socket and use the existing reference-counted timer
> rescheduling path. The next callback reads the current socket again,
> preventing state-machine execution under the old socket's lock. Retain
> the user-ownership check to handle a migration already in progress.
>
> Apply the check to the T3, association, heartbeat, protocol-unreachable,
> reconfiguration and probe timers. Their BH context and the endpoint's
> RCU-deferred sock_put() protect the cached socket during the callback.
> Pair the socket pointer reads with WRITE_ONCE() in sctp_assoc_migrate(),
> taking the new socket reference before updating the pointer.
>
> The issue was discovered and fixed by Qihoo 360's Tulongfeng multi-agent
> system.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> Signed-off-by: liuyong <pkfxxxing@gmail.com>
> ---
>  net/sctp/associola.c     |  4 ++--
>  net/sctp/sm_sideeffect.c | 41 ++++++++++++++++++++--------------------
>  2 files changed, 22 insertions(+), 23 deletions(-)
>
> diff --git a/net/sctp/associola.c b/net/sctp/associola.c
> index 4521be3bd8..c5d520c8d7 100644
> --- a/net/sctp/associola.c
> +++ b/net/sctp/associola.c
> @@ -1088,8 +1088,8 @@ void sctp_assoc_migrate(struct sctp_association *assoc, struct sock *newsk)
>         sctp_endpoint_hold(assoc->ep);
>
>         /* Get a reference to the new sock.  */
> -       assoc->base.sk = newsk;
> -       sock_hold(assoc->base.sk);
> +       sock_hold(newsk);
> +       WRITE_ONCE(assoc->base.sk, newsk);
>
>         /* Add the association to the new endpoint's list of associations.  */
>         sctp_endpoint_add_asoc(newsp->ep, assoc);
> diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c
> index 35f540fb15..bd6b23369f 100644
> --- a/net/sctp/sm_sideeffect.c
> +++ b/net/sctp/sm_sideeffect.c
> @@ -233,15 +233,14 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
>         struct sctp_transport *transport =
>                 timer_container_of(transport, t, T3_rtx_timer);
>         struct sctp_association *asoc = transport->asoc;
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>         int error;
>
> -       /* Check whether a task is in the sock.  */
> -
> +       /* Defer if the socket is busy or the association has migrated. */
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy\n", __func__);
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved\n", __func__);
>
>                 /* Try again later.  */
>                 sctp_transport_hold(transport);
> @@ -271,13 +270,13 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
>  static void sctp_generate_timeout_event(struct sctp_association *asoc,
>                                         enum sctp_event_timeout timeout_type)
>  {
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>         int error = 0;
>
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy: timer %d\n", __func__,
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved: timer %d\n", __func__,
>                          timeout_type);
>
>                 /* Try again later.  */
> @@ -362,7 +361,7 @@ static void sctp_generate_autoclose_event(struct timer_list *t)
>         sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_AUTOCLOSE);
>  }
>
> -/* Generate a heart beat event.  If the sock is busy, reschedule.   Make
> +/* Generate a heart beat event.  If the sock is busy or moved, reschedule.   Make
>   * sure that the transport is still valid.
>   */
>  void sctp_generate_heartbeat_event(struct timer_list *t)
> @@ -370,14 +369,14 @@ void sctp_generate_heartbeat_event(struct timer_list *t)
>         struct sctp_transport *transport = timer_container_of(transport, t,
>                                                               hb_timer);
>         struct sctp_association *asoc = transport->asoc;
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>         u32 elapsed, timeout;
>         int error = 0;
>
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy\n", __func__);
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved\n", __func__);
>
>                 /* Try again later.  */
>                 sctp_transport_hold(transport);
> @@ -418,12 +417,12 @@ void sctp_generate_proto_unreach_event(struct timer_list *t)
>         struct sctp_transport *transport =
>                 timer_container_of(transport, t, proto_unreach_timer);
>         struct sctp_association *asoc = transport->asoc;
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy\n", __func__);
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved\n", __func__);
>
>                 /* Try again later.  */
>                 sctp_transport_hold(transport);
> @@ -454,13 +453,13 @@ void sctp_generate_reconf_event(struct timer_list *t)
>         struct sctp_transport *transport =
>                 timer_container_of(transport, t, reconf_timer);
>         struct sctp_association *asoc = transport->asoc;
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>         int error = 0;
>
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy\n", __func__);
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved\n", __func__);
>
>                 /* Try again later.  */
>                 sctp_transport_hold(transport);
> @@ -492,13 +491,13 @@ void sctp_generate_probe_event(struct timer_list *t)
>         struct sctp_transport *transport = timer_container_of(transport, t,
>                                                               probe_timer);
>         struct sctp_association *asoc = transport->asoc;
> -       struct sock *sk = asoc->base.sk;
> +       struct sock *sk = READ_ONCE(asoc->base.sk);
>         struct net *net = sock_net(sk);
>         int error = 0;
>
>         bh_lock_sock(sk);
> -       if (sock_owned_by_user(sk)) {
> -               pr_debug("%s: sock is busy\n", __func__);
> +       if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
> +               pr_debug("%s: sock is busy or moved\n", __func__);
>
>                 /* Try again later.  */
>                 sctp_transport_hold(transport);
> --
> 2.43.0
>
Please check the sashiko report:

https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261110120212.1110283-2-pkfxxxing%40gmail.com

I think you also need to use READ_ONCE() in sctp_err_lookup() and pass sk
to sctp_v4_err_handle() and sctp_v6_err_handle(), and then add
sk != READ_ONCE(asoc->base.sk) check next to sock_owned_by_user(sk) in the
functions it calls like:
sctp_icmp_frag_needed()/sctp_icmp_proto_unreachable()/sctp_icmp_redirect().

Also, use READ_ONCE() in quic_rcv() and sctp_backlog_rcv().

Thanks.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net v2] sctp: revalidate association sockets after locking
  2026-11-10 12:02 ` [PATCH 1/1] sctp: revalidate the association socket in timer callbacks liuyong
  2026-11-12 18:22   ` Jakub Kicinski
  2026-11-12 21:13   ` Xin Long
@ 2026-11-13  3:28   ` Yong Liu
  2026-11-15 18:09     ` Jakub Kicinski
  2 siblings, 1 reply; 6+ messages in thread
From: Yong Liu @ 2026-11-13  3:28 UTC (permalink / raw)
  To: Marcelo Ricardo Leitner, Xin Long
  Cc: Jakub Kicinski, linux-sctp, netdev, linux-kernel

SCTP timer callbacks and ICMP error lookup cache asoc->base.sk before
acquiring its BH lock. An accept or peeloff can move the association to
a new socket in between. Processing under the old socket lock can then
race with an abort on the new socket that tears down the association's
queues. Transport and association references do not keep those queues
alive. With mdelay() instrumentation widening the race, KASAN reported a
slab-use-after-free in sctp_chunk_abandoned() from the T3 retransmit path.

The ICMP error handlers also reread asoc->base.sk after lookup, so they
can use the new socket without holding its BH lock. In particular, the
PMTU handler can reach the same retransmit path, while protocol-unreachable
handling can run the state machine directly.

Recheck the association's socket after locking it in the timer callbacks.
Treat a mismatch like a busy socket and use the existing reference-counted
retry path. Pass the socket locked by sctp_err_lookup() to both IPv4 and
IPv6 error handlers, including their UDP encapsulation callers. Defer PMTU
and protocol-unreachable processing and ignore redirects when that socket
no longer matches the association. Require a matching socket for immediate
error reports as well.

Use READ_ONCE() for the socket pointer reads in the timer, ICMP, receive
and backlog paths, paired with WRITE_ONCE() in sctp_assoc_migrate(). Take
the new socket reference before publishing its pointer. The cached socket
is protected by BH context and the endpoint's RCU-deferred sock_put().

The issue was discovered and fixed by Qihoo 360's Tulongfeng multi-agent
system.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Yong Liu <pkfxxxing@gmail.com>
---
Changes in v2:
- Use Yong Liu for the author and Signed-off-by, as requested by Jakub
  Kicinski.
- Address Xin Long's review and the Sashiko ICMP finding: pass the locked
  socket to the IPv4/IPv6 error handlers and their UDP encapsulation
  callers, and revalidate it before immediate ICMP processing.
- Use READ_ONCE() for socket pointer reads in sctp_err_lookup(), sctp_rcv()
  and sctp_backlog_rcv().
- Broaden the subject and commit message to cover the ICMP paths.

v1: https://lore.kernel.org/all/20261110120212.1110283-2-pkfxxxing@gmail.com/

Based on b3dfbde62cf1981c4638939c159dfd087b7a72e9 (Linux 7.4.0-rc2).

 net/sctp/associola.c     |  4 ++--
 net/sctp/input.c         | 32 ++++++++++++++++---------------
 net/sctp/ipv6.c          | 11 ++++++-----
 net/sctp/sm_sideeffect.c | 41 ++++++++++++++++++++--------------------
 4 files changed, 45 insertions(+), 43 deletions(-)

diff --git a/net/sctp/associola.c b/net/sctp/associola.c
index 4521be3bd8..c5d520c8d7 100644
--- a/net/sctp/associola.c
+++ b/net/sctp/associola.c
@@ -1088,8 +1088,8 @@ void sctp_assoc_migrate(struct sctp_association *assoc, struct sock *newsk)
 	sctp_endpoint_hold(assoc->ep);
 
 	/* Get a reference to the new sock.  */
-	assoc->base.sk = newsk;
-	sock_hold(assoc->base.sk);
+	sock_hold(newsk);
+	WRITE_ONCE(assoc->base.sk, newsk);
 
 	/* Add the association to the new endpoint's list of associations.  */
 	sctp_endpoint_add_asoc(newsp->ep, assoc);
diff --git a/net/sctp/input.c b/net/sctp/input.c
index 9494cfa511..e25c94ad08 100644
--- a/net/sctp/input.c
+++ b/net/sctp/input.c
@@ -169,7 +169,7 @@ int sctp_rcv(struct sk_buff *skb)
 
 	/* Retrieve the common input handling substructure. */
 	rcvr = asoc ? &asoc->base : &ep->base;
-	sk = rcvr->sk;
+	sk = READ_ONCE(rcvr->sk);
 
 	/*
 	 * RFC 2960, 8.4 - Handle "Out of the blue" Packets.
@@ -217,7 +217,7 @@ int sctp_rcv(struct sk_buff *skb)
 	 */
 	bh_lock_sock(sk);
 
-	if (sk != rcvr->sk) {
+	if (sk != READ_ONCE(rcvr->sk)) {
 		/* Our cached sk is different from the rcvr->sk.  This is
 		 * because migrate()/accept() may have moved the association
 		 * to a new socket and released all the sockets.  So now we
@@ -226,7 +226,7 @@ int sctp_rcv(struct sk_buff *skb)
 		 * of the current sk.
 		 */
 		bh_unlock_sock(sk);
-		sk = rcvr->sk;
+		sk = READ_ONCE(rcvr->sk);
 		bh_lock_sock(sk);
 	}
 
@@ -292,7 +292,7 @@ int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 		goto done;
 	}
 
-	if (unlikely(rcvr->sk != sk)) {
+	if (unlikely(READ_ONCE(rcvr->sk) != sk)) {
 		/* In this case, the association moved from one socket to
 		 * another.  We are currently sitting on the backlog of the
 		 * old socket, so we need to move.
@@ -304,7 +304,7 @@ int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 		 * Otherwise, we can safely push the chunk on the inqueue.
 		 */
 
-		sk = rcvr->sk;
+		sk = READ_ONCE(rcvr->sk);
 		local_bh_disable();
 		bh_lock_sock(sk);
 
@@ -377,7 +377,7 @@ void sctp_icmp_frag_needed(struct sock *sk, struct sctp_association *asoc,
 	     t->pl.probe_size + sctp_transport_pl_hlen(t) <= pmtu))
 		return;
 
-	if (sock_owned_by_user(sk)) {
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
 		atomic_set(&t->mtu_info, pmtu);
 		asoc->pmtu_pending = 1;
 		t->pmtu_pending = 1;
@@ -410,7 +410,8 @@ void sctp_icmp_redirect(struct sock *sk, struct sctp_transport *t,
 {
 	struct dst_entry *dst;
 
-	if (sock_owned_by_user(sk) || !t)
+	if (sock_owned_by_user(sk) || !t ||
+	    sk != READ_ONCE(t->asoc->base.sk))
 		return;
 	dst = sctp_transport_dst_check(t);
 	if (dst)
@@ -432,7 +433,7 @@ void sctp_icmp_proto_unreachable(struct sock *sk,
 			   struct sctp_association *asoc,
 			   struct sctp_transport *t)
 {
-	if (sock_owned_by_user(sk)) {
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
 		if (timer_pending(&t->proto_unreach_timer))
 			return;
 		else {
@@ -492,7 +493,7 @@ struct sock *sctp_err_lookup(struct net *net, int family, struct sk_buff *skb,
 	if (!asoc)
 		return NULL;
 
-	sk = asoc->base.sk;
+	sk = READ_ONCE(asoc->base.sk);
 
 	/* RFC 4960, Appendix C. ICMP Handling
 	 *
@@ -526,7 +527,7 @@ struct sock *sctp_err_lookup(struct net *net, int family, struct sk_buff *skb,
 	/* If too many ICMPs get dropped on busy
 	 * servers this needs to be solved differently.
 	 */
-	if (sock_owned_by_user(sk))
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk))
 		__NET_INC_STATS(net, LINUX_MIB_LOCKDROPPEDICMPS);
 
 	*app = asoc;
@@ -546,11 +547,11 @@ void sctp_err_finish(struct sock *sk, struct sctp_transport *t)
 	sctp_transport_put(t);
 }
 
-static void sctp_v4_err_handle(struct sctp_transport *t, struct sk_buff *skb,
+static void sctp_v4_err_handle(struct sock *sk, struct sctp_transport *t,
+			       struct sk_buff *skb,
 			       __u8 type, __u8 code, __u32 info)
 {
 	struct sctp_association *asoc = t->asoc;
-	struct sock *sk = asoc->base.sk;
 	int err = 0;
 
 	switch (type) {
@@ -582,7 +583,8 @@ static void sctp_v4_err_handle(struct sctp_transport *t, struct sk_buff *skb,
 	default:
 		return;
 	}
-	if (!sock_owned_by_user(sk) && inet_test_bit(RECVERR, sk)) {
+	if (!sock_owned_by_user(sk) && sk == READ_ONCE(asoc->base.sk) &&
+	    inet_test_bit(RECVERR, sk)) {
 		sk->sk_err = err;
 		sk_error_report(sk);
 	} else {  /* Only an error on timeout */
@@ -630,7 +632,7 @@ int sctp_v4_err(struct sk_buff *skb, __u32 info)
 		return -ENOENT;
 	}
 
-	sctp_v4_err_handle(transport, skb, type, code, info);
+	sctp_v4_err_handle(sk, transport, skb, type, code, info);
 	sctp_err_finish(sk, transport);
 
 	return 0;
@@ -660,7 +662,7 @@ int sctp_udp_v4_err(struct sock *sk, struct sk_buff *skb)
 	}
 	if (hdr->type == ICMP_DEST_UNREACH && hdr->code == ICMP_FRAG_NEEDED)
 		info = ntohs(hdr->un.frag.mtu);
-	sctp_v4_err_handle(t, skb, hdr->type, hdr->code, info);
+	sctp_v4_err_handle(sk, t, skb, hdr->type, hdr->code, info);
 
 	sctp_err_finish(sk, t);
 	return 1;
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
index 97390569c4..f96e79ca7f 100644
--- a/net/sctp/ipv6.c
+++ b/net/sctp/ipv6.c
@@ -123,11 +123,11 @@ static struct notifier_block sctp_inet6addr_notifier = {
 	.notifier_call = sctp_inet6addr_event,
 };
 
-static void sctp_v6_err_handle(struct sctp_transport *t, struct sk_buff *skb,
+static void sctp_v6_err_handle(struct sock *sk, struct sctp_transport *t,
+			       struct sk_buff *skb,
 			       __u8 type, __u8 code, __u32 info)
 {
 	struct sctp_association *asoc = t->asoc;
-	struct sock *sk = asoc->base.sk;
 	int err = 0;
 
 	switch (type) {
@@ -149,7 +149,8 @@ static void sctp_v6_err_handle(struct sctp_transport *t, struct sk_buff *skb,
 	}
 
 	icmpv6_err_convert(type, code, &err);
-	if (!sock_owned_by_user(sk) && inet6_test_bit(RECVERR6, sk)) {
+	if (!sock_owned_by_user(sk) && sk == READ_ONCE(asoc->base.sk) &&
+	    inet6_test_bit(RECVERR6, sk)) {
 		sk->sk_err = err;
 		sk_error_report(sk);
 	} else {
@@ -181,7 +182,7 @@ static int sctp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 		return -ENOENT;
 	}
 
-	sctp_v6_err_handle(transport, skb, type, code, ntohl(info));
+	sctp_v6_err_handle(sk, transport, skb, type, code, ntohl(info));
 	sctp_err_finish(sk, transport);
 
 	return 0;
@@ -211,7 +212,7 @@ int sctp_udp_v6_err(struct sock *sk, struct sk_buff *skb)
 	}
 	if (hdr->icmp6_type == ICMPV6_PKT_TOOBIG)
 		info = ntohl(hdr->icmp6_mtu);
-	sctp_v6_err_handle(t, skb, hdr->icmp6_type, hdr->icmp6_code, info);
+	sctp_v6_err_handle(sk, t, skb, hdr->icmp6_type, hdr->icmp6_code, info);
 
 	sctp_err_finish(sk, t);
 	return 1;
diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c
index 35f540fb15..bd6b23369f 100644
--- a/net/sctp/sm_sideeffect.c
+++ b/net/sctp/sm_sideeffect.c
@@ -233,15 +233,14 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, T3_rtx_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error;
 
-	/* Check whether a task is in the sock.  */
-
+	/* Defer if the socket is busy or the association has migrated. */
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -271,13 +270,13 @@ void sctp_generate_t3_rtx_event(struct timer_list *t)
 static void sctp_generate_timeout_event(struct sctp_association *asoc,
 					enum sctp_event_timeout timeout_type)
 {
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy: timer %d\n", __func__,
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved: timer %d\n", __func__,
 			 timeout_type);
 
 		/* Try again later.  */
@@ -362,7 +361,7 @@ static void sctp_generate_autoclose_event(struct timer_list *t)
 	sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_AUTOCLOSE);
 }
 
-/* Generate a heart beat event.  If the sock is busy, reschedule.   Make
+/* Generate a heart beat event.  If the sock is busy or moved, reschedule.   Make
  * sure that the transport is still valid.
  */
 void sctp_generate_heartbeat_event(struct timer_list *t)
@@ -370,14 +369,14 @@ void sctp_generate_heartbeat_event(struct timer_list *t)
 	struct sctp_transport *transport = timer_container_of(transport, t,
 							      hb_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	u32 elapsed, timeout;
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -418,12 +417,12 @@ void sctp_generate_proto_unreach_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, proto_unreach_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -454,13 +453,13 @@ void sctp_generate_reconf_event(struct timer_list *t)
 	struct sctp_transport *transport =
 		timer_container_of(transport, t, reconf_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
@@ -492,13 +491,13 @@ void sctp_generate_probe_event(struct timer_list *t)
 	struct sctp_transport *transport = timer_container_of(transport, t,
 							      probe_timer);
 	struct sctp_association *asoc = transport->asoc;
-	struct sock *sk = asoc->base.sk;
+	struct sock *sk = READ_ONCE(asoc->base.sk);
 	struct net *net = sock_net(sk);
 	int error = 0;
 
 	bh_lock_sock(sk);
-	if (sock_owned_by_user(sk)) {
-		pr_debug("%s: sock is busy\n", __func__);
+	if (sock_owned_by_user(sk) || sk != READ_ONCE(asoc->base.sk)) {
+		pr_debug("%s: sock is busy or moved\n", __func__);
 
 		/* Try again later.  */
 		sctp_transport_hold(transport);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net v2] sctp: revalidate association sockets after locking
  2026-11-13  3:28   ` [PATCH net v2] sctp: revalidate association sockets after locking Yong Liu
@ 2026-11-15 18:09     ` Jakub Kicinski
  0 siblings, 0 replies; 6+ messages in thread
From: Jakub Kicinski @ 2026-11-15 18:09 UTC (permalink / raw)
  To: Yong Liu
  Cc: Marcelo Ricardo Leitner, Xin Long, linux-sctp, netdev,
	linux-kernel

On Fri, 13 Nov 2026 11:28:02 +0800 Yong Liu wrote:
> Subject: [PATCH net v2] sctp: revalidate association sockets after locking

1. please use the problem you're solving for the subject, don't
describe the code change, so that the subject doesn't change between
revisions
2. please don't send new versions in reply to old ones

> SCTP timer callbacks and ICMP error lookup cache asoc->base.sk before
> acquiring its BH lock. An accept or peeloff can move the association to
> a new socket in between. Processing under the old socket lock can then
> race with an abort on the new socket that tears down the association's
> queues. Transport and association references do not keep those queues
> alive. With mdelay() instrumentation widening the race, KASAN reported a
> slab-use-after-free in sctp_chunk_abandoned() from the T3 retransmit path.
> 
> The ICMP error handlers also reread asoc->base.sk after lookup, so they
> can use the new socket without holding its BH lock. In particular, the
> PMTU handler can reach the same retransmit path, while protocol-unreachable
> handling can run the state machine directly.
> 
> Recheck the association's socket after locking it in the timer callbacks.
> Treat a mismatch like a busy socket and use the existing reference-counted
> retry path. Pass the socket locked by sctp_err_lookup() to both IPv4 and
> IPv6 error handlers, including their UDP encapsulation callers. Defer PMTU
> and protocol-unreachable processing and ignore redirects when that socket
> no longer matches the association. Require a matching socket for immediate
> error reports as well.
> 
> Use READ_ONCE() for the socket pointer reads in the timer, ICMP, receive
> and backlog paths, paired with WRITE_ONCE() in sctp_assoc_migrate(). Take
> the new socket reference before publishing its pointer. The cached socket
> is protected by BH context and the endpoint's RCU-deferred sock_put().
> 
> The issue was discovered and fixed by Qihoo 360's Tulongfeng multi-agent
> system.

3. please remove this, there's no place in commit message for
advertising
-- 
pw-bot: cr

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-15 18:09 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 12:02 [PATCH 0/1] sctp: fix timer locking across association migration liuyong
2026-11-10 12:02 ` [PATCH 1/1] sctp: revalidate the association socket in timer callbacks liuyong
2026-11-12 18:22   ` Jakub Kicinski
2026-11-12 21:13   ` Xin Long
2026-11-13  3:28   ` [PATCH net v2] sctp: revalidate association sockets after locking Yong Liu
2026-11-15 18:09     ` Jakub Kicinski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox