mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v5] net: pin protocol module before inet socket allocation
@ 2026-10-13  7:56 Chengfeng Ye
  0 siblings, 0 replies; only message 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	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-13  7:56 UTC | newest]

Thread overview: (only message) (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

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

all inboxes | Powered by JetHome®