* Re: [PATCH net] ipv6: Keep the SCTP provider alive through socket allocation
2026-10-12 4:42 [PATCH net] ipv6: Keep the SCTP provider alive through socket allocation Cen Zhang
@ 2026-10-12 19:56 ` Kuniyuki Iwashima
2026-10-13 2:39 ` Cen Zhang
2026-10-13 5:44 ` netdev-bot+sashiko
1 sibling, 1 reply; 4+ messages in thread
From: Kuniyuki Iwashima @ 2026-10-12 19:56 UTC (permalink / raw)
To: Cen Zhang
Cc: davem, edumazet, kuba, pabeni, horms, fmancera, willemb, kees,
netdev, linux-kernel, baijiaju1990, jjzuming, Chengfeng Ye
On Sun, Oct 11, 2026 at 9:42 PM Cen Zhang <zzzccc427@gmail.com> wrote:
>
> The sctpv6_prot slab must remain alive through socket allocation.
> inet6_create() selects that proto under RCU, but drops RCU before
> sk_alloc(). sk_prot_alloc() obtains the owner reference only after
> allocating from the slab.
>
> SCTP publishes its IPv6 stream and seqpacket entries while its module
> is still COMING. A concurrent user socket can select an entry before
> register_pernet_subsys(&sctp_ctrlsock_ops), sctp_v4_add_protocol() or
> sctp_v6_add_protocol() fails. The subsequent initialization unwind
> removes both entries and destroys the cache without waiting for owner
> references.
>
> After the constructor captures the cache pointer, initialization can
> fail and destroy the cache before the allocator consumes that pointer:
>
> User socket creation SCTP module initialization
> -------------------- --------------------------
> publish IPv6 protosw entries
> select sctpv6_prot under RCU
> drop RCU
> capture prot->slab
> fail a later setup step
> sctp_v6_protosw_exit()
> unlink both entries
> synchronize_net()
> proto_unregister()
> destroy slab cache
> kmem_cache_alloc(saved slab)
>
> The allocator can then read retired cache metadata and fault. The grace
> period drains lookup readers, but the constructor has already left RCU.
> An early owner reference alone cannot prevent initialization failure
> from destroying the cache, since try_module_get() accepts COMING.
>
> The initialization-failure path follows from source inspection. The
> Oops below came from an unload experiment using a modified kernel with
> allocator bootstrap changes, scheduling instrumentation and SCTP's
> internal control socket omitted. With that socket present, its owner
> reference blocks ordinary unload. The experiment selected and captured
> the same cache that teardown destroyed; the seqpacket constructor then
> faulted before the allocator returned.
>
> Reject user SCTP sockets while the owner is COMING and take a temporary
> owner reference under the lookup's RCU protection before dropping RCU.
> User allocation then requires completed initialization, and the
> reference prevents normal module exit until sk_alloc() takes the
> socket's own reference or fails. Release the temporary reference in
> either case. Keep kernel control socket creation allowed during init,
> where SCTP's per-net registration orders construction and cleanup.
>
> KASAN report as below:
>
> Oops: general protection fault, probably for non-canonical address
> 0xffff11036c3d1430: 0000 [#1] SMP KASAN NOPTI
> KASAN: maybe wild-memory-access in range
> [0xfff8a81b61e8a180-0xfff8a81b61e8a187]
> CPU: 2 UID: 0 PID: 532 Comm: sctp-race-fixtu Not tainted
> 7.2.0-rc5-pmb-ipcore-functional-v2+ #1 PREEMPT(lazy)
> 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
> RIP: 0010:kmem_cache_alloc_noprof+0xd6/0x600
> Code: 85 c0 0f 8e 93 04 00 00 44 8b 74 24 18 0f 1f 44 00 00 41 bf ff ff
> ff ff 65 ff 05 b5 e6 0d 09 65 4c 8b 2d a5 e6 0d 09 4c 03 2b <41> 0f b6
> 45 30 84 c0 0f 85 11 04 00 00 41 c6 45 30 01 45 31 c9 41
> RSP: 0018:ffff8881135dfcc0 EFLAGS: 00010287
> RAX: 0000000000000001 RBX: ffff88810891ec80 RCX: dffffc0000000000
> RDX: 0000000000000cc0 RSI: 0000000000000890 RDI: ffff88810891ec80
> RBP: 0000000000000c00 R08: 0000000000000001 R09: 00000000000000bd
> R10: 0000000000000028 R11: ffff888113e5ba00 R12: 0000000000000cc0
> R13: ffff11036c3d1400 R14: 0000000000000000 R15: 00000000ffffffff
> FS: 00007ff3c2206780(0000) GS:ffff88826b86a000(0000)
> knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 000055b973e7b10c CR3: 00000001158b5005 CR4: 0000000000770ef0
> PKRU: 55555554
> Call Trace:
> <TASK>
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? sk_prot_alloc+0x6f/0x2c0
> sk_prot_alloc+0x6f/0x2c0
> sk_alloc+0x37/0xb40
> inet6_create+0x31b/0x1230
> ? inet6_create+0x5a/0x1230
> __sock_create+0x2c4/0x730
> __sys_socket+0x141/0x210
> ? __pfx___sys_socket+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? handle_mm_fault+0x12e/0x580
> __x64_sys_socket+0x77/0xc0
> do_syscall_64+0x115/0x6a0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7ff3c231b437
> Code: 8b 54 24 08 64 48 2b 14 25 28 00 00 00 75 05 48 83 c4 18 c3 e8 ba
> a7 00 00 66 2e 0f 1f 84 00 00 00 00 00 b8 29 00 00 00 0f 05 <48> 3d 01
> f0 ff ff 73 01 c3 48 8b 0d a9 29 0d 00 f7 d8 64 89 01 48
> RSP: 002b:00007ffed24e4ab8 EFLAGS: 00000246 ORIG_RAX: 0000000000000029
> RAX: ffffffffffffffda RBX: 00007ffed24e4bd8 RCX: 00007ff3c231b437
> RDX: 0000000000000084 RSI: 0000000000000005 RDI: 000000000000000a
> RBP: 0000000000000002 R08: 0000000000000000 R09: 00007ff3c2413aa0
> R10: 00007ffed24e4800 R11: 0000000000000246 R12: 0000000000000000
> R13: 00007ffed24e4bf0 R14: 00007ff3c2444000 R15: 000055b973e7cd48
> </TASK>
> Modules linked in: [last unloaded: sctp]
> ---[ end trace 0000000000000000 ]---
> RIP: 0010:kmem_cache_alloc_noprof+0xd6/0x600
> Code: 85 c0 0f 8e 93 04 00 00 44 8b 74 24 18 0f 1f 44 00 00 41 bf ff ff
> ff ff 65 ff 05 b5 e6 0d 09 65 4c 8b 2d a5 e6 0d 09 4c 03 2b <41> 0f b6
> 45 30 84 c0 0f 85 11 04 00 00 41 c6 45 30 01 45 31 c9 41
> RSP: 0018:ffff8881135dfcc0 EFLAGS: 00010287
> RAX: 0000000000000001 RBX: ffff88810891ec80 RCX: dffffc0000000000
> RDX: 0000000000000cc0 RSI: 0000000000000890 RDI: ffff88810891ec80
> RBP: 0000000000000c00 R08: 0000000000000001 R09: 00000000000000bd
> R10: 0000000000000028 R11: ffff888113e5ba00 R12: 0000000000000cc0
> R13: ffff11036c3d1400 R14: 0000000000000000 R15: 00000000ffffffff
> FS: 00007ff3c2206780(0000) GS:ffff88826b86a000(0000)
> knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 000055b973e7b10c CR3: 00000001158b5005 CR4: 0000000000770ef0
> PKRU: 55555554
> Kernel panic - not syncing: Fatal exception
> Kernel Offset: disabled
> ---[ end Kernel panic - not syncing: Fatal exception ]---
>
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
> Assisted-by: LLM
>
> ---
>
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index a9b8b4b42a1af72f09fb9544b0e13aade00a1e3d..fb7f84baeeb66f5d3926169df790cc439f98224f 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
> @@ -110,6 +110,7 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
> struct sock *sk;
> struct inet_protosw *answer;
> struct proto *answer_prot;
> + struct module *owner = NULL;
> unsigned char answer_flags;
> int try_loading_module = 0;
> int err;
> @@ -169,6 +170,17 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
>
> sock->ops = answer->ops;
> answer_prot = answer->prot;
> + if (answer->protocol == IPPROTO_SCTP) {
Chengfeng Ye is working on the same issue, and this is not limited to sctp.
(see v3 for sctp)
https://lore.kernel.org/netdev/20261002173844.3537111-1-nicoyip.dev@gmail.com/
> + owner = answer_prot->owner;
> + err = -EPROTONOSUPPORT;
> + /* SCTP init can unwind despite an owner reference.
> + * Allow the kernel control socket during initialization.
> + */
> + if (!kern && owner && module_is_coming(owner))
> + goto out_rcu_unlock;
> + if (!try_module_get(owner))
> + goto out_rcu_unlock;
> + }
> answer_flags = answer->flags;
> rcu_read_unlock();
>
> @@ -176,6 +188,7 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
>
> err = -ENOBUFS;
> sk = sk_alloc(net, PF_INET6, GFP_KERNEL, answer_prot, kern);
> + module_put(owner);
> if (!sk)
> goto out;
>
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH net] ipv6: Keep the SCTP provider alive through socket allocation
2026-10-12 4:42 [PATCH net] ipv6: Keep the SCTP provider alive through socket allocation Cen Zhang
2026-10-12 19:56 ` Kuniyuki Iwashima
@ 2026-10-13 5:44 ` netdev-bot+sashiko
1 sibling, 0 replies; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-10-13 5:44 UTC (permalink / raw)
To: zzzccc427
Cc: davem, edumazet, kuba, pabeni, horms, kuniyu, fmancera, willemb,
kees, netdev, linux-kernel, baijiaju1990, jjzuming
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 2 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The fix only covers IPv6.
- [Medium] The COMING check in inet6_create() is skipped for every kernel
socket (`!kern && owner && module_is_coming(owner)`), not just SCTP's…
- [Medium] inet6_create() now fails a user socket(AF_INET6,
SOCK_STREAM/SOCK_SEQPACKET, IPPROTO_SCTP) with -EPROTONOSUPPORT if the…
- [Low] The commit fixes a user-triggerable kernel fault (GPF in
kmem_cache_alloc reached from socket(2)), but its trailers are only…
--
ipv6: Keep the SCTP provider alive through socket allocation
This patch makes inet6_create() reject user SCTP sockets while the SCTP
module is still in MODULE_STATE_COMING. It also takes a temporary
reference on the SCTP proto owner under RCU and holds it across
sk_alloc(). The goal is to stop a failing sctp_init() unwind from
destroying the sctpv6_prot slab while a socket constructor still holds
the cache pointer.
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
> Assisted-by: LLM
[Severity: Low]
This isn't a bug, but the commit message describes a fault that socket(2)
can reach, and the only trailers are Signed-off-by and Assisted-by.
Could a Fixes: tag naming the commit that opened this window be added,
along with Cc: stable, so backporters know which trees are affected?
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index 282912a119999..3873f1f50accb 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
[ ... ]
> @@ -169,6 +170,17 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
>
> sock->ops = answer->ops;
> answer_prot = answer->prot;
> + if (answer->protocol == IPPROTO_SCTP) {
> + owner = answer_prot->owner;
> + err = -EPROTONOSUPPORT;
> + /* SCTP init can unwind despite an owner reference.
> + * Allow the kernel control socket during initialization.
> + */
> + if (!kern && owner && module_is_coming(owner))
[Severity: Medium]
The comment says only the SCTP kernel control socket is allowed during
initialization. This check lets every kern=1 caller through, though. Is
that the intended scope?
As the commit message notes, try_module_get() succeeds on a COMING module.
For a kernel caller, the reference taken below therefore does not block
the init unwind. Only sctp_ctl_sock_init() is ordered against that unwind,
because unregister_pernet_subsys(&sctp_ctrlsock_ops) runs before
sctp_v6_protosw_exit().
Other in-tree kernel SCTP users have no such ordering. For example,
fs/dlm/lowcomms.c does:
result = sock_create_kern(&init_net, dlm_local_addr[0].ss_family,
SOCK_STREAM, dlm_proto_ops->proto, &sock);
Here dlm_sctp_ops.proto is IPPROTO_SCTP, and ss_family can be AF_INET6.
Suppose such a caller finds the sctpv6 entry while SCTP is COMING, and a
later sctp_init() step then fails:
inet6_create(kern=1) sctp_init() failure path
-------------------- ------------------------
try_module_get(owner) succeeds
rcu_read_unlock()
sctp_v6_protosw_exit()
proto_unregister(&sctpv6_prot)
do_init_module() fail:
module_put(mod)
free_module(mod)
sk_alloc(..., answer_prot, kern)
module_put(owner)
Could sk_alloc() then allocate from a destroyed kmem_cache, and could
module_put() then touch a freed struct module? On init failure,
do_init_module() frees the module without waiting for the refcount to
drop.
> + goto out_rcu_unlock;
[Severity: Medium]
Is -EPROTONOSUPPORT the intended user-visible result here?
Before this patch, socket(AF_INET6, SOCK_STREAM or SOCK_SEQPACKET,
IPPROTO_SCTP) succeeded if it ran while the module was COMING. Now it
jumps straight to out_rcu_unlock and skips the try_loading_module /
request_module() retry. That retry would have waited for the load in
progress, and the re-lookup would then have found a LIVE module.
The window is not small. sctp_init() publishes the IPv6 entries in
sctp_v6_protosw_init(). It then runs register_pernet_subsys(
&sctp_ctrlsock_ops), which creates one control socket per netns, followed
by sctp_v4_add_protocol(), sctp_v6_add_protocol() and sctp_offload_init().
Only after all of that does do_init_module() set MODULE_STATE_LIVE.
Say one thread triggers the autoload and a second thread calls socket()
after the entries are published. Would the second thread get "protocol
not supported" for a protocol that is moments away from being usable?
> + if (!try_module_get(owner))
> + goto out_rcu_unlock;
> + }
[Severity: High]
Does inet_create() in net/ipv4/af_inet.c still have the same race during
the same sctp_init() unwind?
sctp_init() calls sctp_v4_protosw_init() first. That registers sctp_prot
and publishes sctp_seqpacket_protosw and sctp_stream_protosw while the
module is still COMING. Every later failure ends up in
sctp_v4_protosw_exit():
net/sctp/protocol.c:sctp_init() {
...
status = sctp_v6_protosw_init();
if (status)
goto err_v6_protosw_init;
status = register_pernet_subsys(&sctp_ctrlsock_ops);
...
err_v6_protosw_init:
sctp_v4_protosw_exit();
...
}
sctp_v4_protosw_exit() finishes with proto_unregister(&sctp_prot), which
destroys the slab.
Meanwhile, inet_create() has neither the COMING check nor the temporary
owner reference:
net/ipv4/af_inet.c:inet_create() {
...
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_flags = answer->flags;
rcu_read_unlock();
WARN_ON(!answer_prot->slab);
err = -ENOMEM;
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
...
}
Could an unprivileged socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP) still
reach kmem_cache_alloc() on a destroyed cache? That is the same fault this
commit describes, and the commit message does not mention IPv4.
It also leaves the two constructors behaving differently while the module
is COMING: user AF_INET6 SCTP sockets now fail with -EPROTONOSUPPORT, but
AF_INET SCTP sockets go ahead and stay exposed. Should inet_create() get
the same protection?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/pm-ip-core-objects-candidate-0001-v2-efc0e8a00d4f22c36ab8%40gmail.com
^ permalink raw reply [flat|nested] 4+ messages in thread