* [PATCH net v5] net: pin protocol module before inet socket allocation
@ 2026-10-13 7:56 Chengfeng Ye
2026-10-14 8:31 ` netdev-bot+sashiko
0 siblings, 1 reply; 2+ messages in thread
From: Chengfeng Ye @ 2026-10-13 7:56 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni
Cc: kuniyu, horms, fmancera, netdev, linux-kernel, stable,
Chengfeng Ye
inet_create() and inet6_create() look up the protocol under RCU, then drop
RCU before using the resulting proto. A concurrent module unload can
unregister the protosw, wait for the RCU readers, and destroy the proto's
slab before sk_alloc() uses it. sk_alloc() may sleep, so the RCU read-side
critical section cannot simply be extended across the allocation.
An LLM-based source scan identified this issue. It was reproduced with
l2tp_ip by racing socket(AF_INET, SOCK_DGRAM, IPPROTO_L2TP) against module
unload in a KASAN-enabled QEMU guest. The reproduction used a temporary
50 ms delay between reading prot->slab and calling kmem_cache_alloc().
proto_unregister() destroyed the cache before the allocation used it:
Oops: general protection fault, probably for non-canonical address
0xffff1108974182a0
KASAN: maybe wild-memory-access in range
[0xfff8a844ba0c1500-0xfff8a844ba0c1507]
RIP: kmem_cache_alloc_noprof+0x63/0x370
Call Trace:
sk_prot_alloc+0x74/0x2c0
sk_alloc+0x2b/0x6c0
inet_create+0x2cd/0xd40
__sock_create+0x1c3/0x430
__sys_socket+0x116/0x1d0
Cache the proto and its owner under RCU and acquire a temporary module
reference before dropping RCU. This prevents normal module unload while
the proto is in use. sk_prot_alloc() takes the socket's own reference;
drop the temporary reference on every exit after allocation. Use the
cached proto for the IPv6 backlog callback as well.
A failed module initialization can unwind published protocols despite
module references. Reject userspace socket creation while the owner is
MODULE_STATE_COMING, before dropping RCU. Exempt kernel sockets from this
state check: SCTP creates its kernel control socket from pernet init
before module initialization completes. These sockets pass kern=true,
which avoids pinning the network namespace they belong to. Both kernel
and userspace sockets still acquire the temporary module reference with
try_module_get().
Fixes: a79af59efd20 ("[NET]: Fix module reference counts for loadable protocol modules")
Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
Cc: stable@vger.kernel.org
Assisted-by: GPT-6-Astra
Assisted-by: OpenAI Codex
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
---
Changes in v5:
- Reject MODULE_STATE_COMING only for userspace sockets (!kern), as
suggested by Kuniyuki. Preserve SCTP kernel control socket creation
during module initialization and keep the temporary module reference
for both kernel and userspace sockets.
- Document how the bug was discovered and the test-only delay used in
the original KASAN reproduction, as requested by the discovery bot
and Jakub. Retain the original fault addresses.
- Keep the cached owner, balanced temporary reference and cached IPv6
backlog callback from v4.
- Rebase onto net/main at 8e2fbe0e75d3.
Validation:
- W=1 build with modular SCTP and compilation of both changed objects
with CONFIG_MODULES=n.
- SCTP module loading and 20 socket/netns cycles per boot on baseline
and v5, with IPv6 enabled and disabled; module references returned
to their initial count after namespace cleanup.
v4: https://lore.kernel.org/r/20261002173844.3537111-1-nicoyip.dev@gmail.com/
Review: https://lore.kernel.org/r/20261012200752.3771786-1-kuniyu@google.com/
v3: https://lore.kernel.org/r/20260904111514.584264-1-nicoyip.dev@gmail.com/
v3 review: https://lore.kernel.org/r/178887699602.219967.13594894510710999619@kernel.org/
v2: https://lore.kernel.org/r/20260825172349.232794-1-nicoyip.dev@gmail.com/
v1: https://lore.kernel.org/r/20260823171311.3857087-1-nicoyip.dev@gmail.com/
net/ipv4/af_inet.c | 15 ++++++++++++---
net/ipv6/af_inet6.c | 17 +++++++++++++----
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 14ce01092fda6..5c02022c6c182 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -263,6 +263,7 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
struct inet_protosw *answer;
struct inet_sock *inet;
struct proto *answer_prot;
+ struct module *answer_owner;
unsigned char answer_flags;
int try_loading_module = 0;
int err;
@@ -322,9 +323,15 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
!ns_capable(net->user_ns, CAP_NET_RAW))
goto out_rcu_unlock;
- sock->ops = answer->ops;
answer_prot = answer->prot;
+ answer_owner = answer_prot->owner;
answer_flags = answer->flags;
+ if ((!kern && answer_owner && module_is_coming(answer_owner)) ||
+ !try_module_get(answer_owner)) {
+ err = -EPROTONOSUPPORT;
+ goto out_rcu_unlock;
+ }
+ sock->ops = answer->ops;
rcu_read_unlock();
WARN_ON(!answer_prot->slab);
@@ -332,7 +339,7 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
err = -ENOMEM;
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
if (!sk)
- goto out;
+ goto out_module_put;
err = 0;
if (INET_PROTOSW_REUSE & answer_flags)
@@ -398,6 +405,8 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
if (err)
goto out_sk_release;
}
+out_module_put:
+ module_put(answer_owner);
out:
return err;
out_rcu_unlock:
@@ -406,7 +415,7 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
out_sk_release:
sk_common_release(sk);
sock->sk = NULL;
- goto out;
+ goto out_module_put;
}
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 282912a119999..99e57fd643322 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 *answer_owner;
unsigned char answer_flags;
int try_loading_module = 0;
int err;
@@ -167,9 +168,15 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
!ns_capable(net->user_ns, CAP_NET_RAW))
goto out_rcu_unlock;
- sock->ops = answer->ops;
answer_prot = answer->prot;
+ answer_owner = answer_prot->owner;
answer_flags = answer->flags;
+ if ((!kern && answer_owner && module_is_coming(answer_owner)) ||
+ !try_module_get(answer_owner)) {
+ err = -EPROTONOSUPPORT;
+ goto out_rcu_unlock;
+ }
+ sock->ops = answer->ops;
rcu_read_unlock();
WARN_ON(!answer_prot->slab);
@@ -177,7 +184,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);
if (!sk)
- goto out;
+ goto out_module_put;
sock_init_data(sock, sk);
@@ -201,7 +208,7 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
sk->sk_family = PF_INET6;
sk->sk_protocol = protocol;
- sk->sk_backlog_rcv = answer->prot->backlog_rcv;
+ sk->sk_backlog_rcv = answer_prot->backlog_rcv;
inet_sk(sk)->pinet6 = np = inet6_sk_generic(sk);
np->hop_limit = -1;
@@ -251,6 +258,8 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
if (err)
goto out_sk_release;
}
+out_module_put:
+ module_put(answer_owner);
out:
return err;
out_rcu_unlock:
@@ -259,7 +268,7 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol,
out_sk_release:
sk_common_release(sk);
sock->sk = NULL;
- goto out;
+ goto out_module_put;
}
int __inet6_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
base-commit: 8e2fbe0e75d38690d551456658194c632933cb57
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH net v5] net: pin protocol module before inet socket allocation
2026-10-13 7:56 [PATCH net v5] net: pin protocol module before inet socket allocation Chengfeng Ye
@ 2026-10-14 8:31 ` netdev-bot+sashiko
0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-10-14 8:31 UTC (permalink / raw)
To: nicoyip.dev
Cc: davem, edumazet, kuba, pabeni, kuniyu, horms, fmancera, netdev,
linux-kernel, stable
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · Low: 0
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 patch closes the failed-module-init lifetime race only for
userspace sockets.
- [Medium] A userspace socket() call can now fail with -EPROTONOSUPPORT
even though the protocol module is loading successfully.
--
net: pin protocol module before inet socket allocation
This patch takes a temporary module reference on the protocol owner in
inet_create() and inet6_create() before dropping RCU, so a concurrent
module unload cannot destroy the proto slab before sk_alloc() uses it. It
also rejects userspace socket creation while the owner module is still
MODULE_STATE_COMING, and exempts kernel sockets from that check.
> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
> index 14ce01092fda6..5c02022c6c182 100644
> --- a/net/ipv4/af_inet.c
> +++ b/net/ipv4/af_inet.c
[ ... ]
> @@ -322,9 +323,15 @@ static int inet_create(struct net *net, struct socket *sock, int protocol,
> !ns_capable(net->user_ns, CAP_NET_RAW))
> goto out_rcu_unlock;
>
> - sock->ops = answer->ops;
> answer_prot = answer->prot;
> + answer_owner = answer_prot->owner;
> answer_flags = answer->flags;
> + if ((!kern && answer_owner && module_is_coming(answer_owner)) ||
> + !try_module_get(answer_owner)) {
> + err = -EPROTONOSUPPORT;
> + goto out_rcu_unlock;
> + }
[Severity: Medium]
Can a userspace socket() call now fail with -EPROTONOSUPPORT while the
protocol module is loading and will finish successfully?
do_init_module() sets MODULE_STATE_LIVE only after mod->init() returns.
Anything published from inside init is therefore seen as COMING until
then.
Before this patch, the only check was try_module_get(prot->owner) in
sk_prot_alloc(). module_is_live() only rejects MODULE_STATE_GOING, so that
check succeeds for COMING modules and the socket was created.
For SCTP, sctp_init() publishes the protosw early and does more work after
that:
net/sctp/protocol.c:sctp_init() {
...
status = sctp_v4_protosw_init();
...
status = sctp_v6_protosw_init();
...
status = register_pernet_subsys(&sctp_ctrlsock_ops);
...
}
register_pernet_subsys() creates a control socket in every netns.
sctp_v4_add_protocol(), sctp_v6_add_protocol() and sctp_offload_init()
come after it. So the window gets longer as the number of network
namespaces grows.
Suppose a daemon calls socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP) while
modules-load.d, or another socket() autoload, is loading sctp. It would
now get EPROTONOSUPPORT, and applications usually read that as "protocol
not supported" and do not retry.
A second concurrent autoloader can hit this too. If its request_module()
returns while the module is still COMING, the retried lookup finds the
protosw and this check then fails the call.
Would it work to drop RCU, wait on module_wq until the owner leaves
MODULE_STATE_COMING, and then retry the lookup, instead of returning an
error straight away? inet6_create() in net/ipv6/af_inet6.c has the same
check.
[Severity: High]
Does the !kern short-circuit leave the failed-init case open for kernel
sockets created by other subsystems?
The commit message justifies the exemption with SCTP's own per-netns
control sockets. Those are destroyed by sctp_init()'s unwind through
unregister_pernet_subsys(&sctp_ctrlsock_ops). However, every kern caller
skips the COMING check, and try_module_get() succeeds on a COMING module.
DLM, for example, creates SCTP sockets with kern set:
fs/dlm/lowcomms.c:dlm_connect() {
...
result = sock_create_kern(&init_net, dlm_local_addr[0].ss_family,
SOCK_STREAM, dlm_proto_ops->proto, &sock);
...
}
The DLM listen path does the same.
Consider this sequence:
1. sctp is COMING, and sctp_v4_protosw_init() has already published
sctp_stream_protosw.
2. DLM's sock_create_kern() reaches inet_create() with kern=1 and skips
the COMING check. try_module_get() succeeds, and sk_alloc() allocates
from sctp_prot.slab.
3. A later step in sctp_init() fails, for example ENOMEM in
register_pernet_subsys(&sctp_ctrlsock_ops), or a failure in
sctp_v4_add_protocol() or sctp_v6_add_protocol(). The unwind runs:
err_register_ctrlsock:
sctp_v6_protosw_exit();
err_v6_protosw_init:
sctp_v4_protosw_exit();
This reaches proto_unregister(&sctp_prot), which calls
kmem_cache_destroy(prot->slab) while the DLM socket is still allocated
in that cache.
4. The failure path in do_init_module() sets MODULE_STATE_GOING and calls
synchronize_rcu() and module_put(). It then calls free_module()
regardless of the remaining refcount.
After that, does the DLM socket's sk->sk_prot point into freed module
memory? If so, the next sendmsg, close or release on it would be a
use-after-free.
Neither the temporary reference added here nor the one taken in
sk_prot_alloc() stops free_module() on that failure path.
The underlying race is older than this patch. But the commit message
lists failed module initialization as a case this patch handles, and the
kern exemption keeps it reachable. Should the COMING rejection also apply
to kern sockets that the owning module did not create itself? The same
question applies to inet6_create().
> + sock->ops = answer->ops;
> rcu_read_unlock();
>
> WARN_ON(!answer_prot->slab);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261013075604.868273-1-nicoyip.dev%40gmail.com
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-14 8:31 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 7:56 [PATCH net v5] net: pin protocol module before inet socket allocation Chengfeng Ye
2026-10-14 8:31 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox