[PATCH net v2 2/2] gtp: fix use-after-free during GTP device teardown
From: Cen Zhang (Microsoft) <hidden>
Date: 2026-08-25 05:24:12
Also in:
lkml
Subsystem:
gtp (gprs tunneling protocol), networking drivers, the rest · Maintainers:
Pablo Neira Ayuso, Harald Welte, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
gtp_encap_disable() releases the kernel-created sockets while
sk_created is still true and without waiting for in-flight readers.
This allows two concurrent use-after-free scenarios:
1. A softirq packet handler that already observed sk_created == true
via smp_load_acquire() can dereference sk0/sk1u after they have
been freed.
2. gtp_genl_send_echo_req() runs in process context without RTNL.
synchronize_net() waits for RCU-protected softirq handlers but
does not cover this non-RCU generic netlink reader, which can
dereference freed sk0/sk1u during concurrent teardown:
RIP: 0010:ip4_route_output_gtp (drivers/net/gtp.c)
gtp_genl_send_echo_req
Kernel panic - not syncing: Fatal exception
Reorder gtp_encap_disable() to clear sk_created first, then call
synchronize_net() to wait for in-flight softirq handlers before
releasing the sockets. Hold RTNL in gtp_genl_send_echo_req() to
serialize with teardown for the process-context path. Under RTNL,
the smp_load_acquire() from patch 1/2 becomes redundant and is
replaced with a plain read.
Fixes: b20dc3c68458 ("gtp: Allow to create GTP device without FDs")
Fixes: d33bd757d362 ("gtp: Implement GTP echo request")
Reported-by: AutonomousCodeSecurity@microsoft.com
Reported-by: Cen Zhang (Microsoft) <redacted>
Signed-off-by: Cen Zhang (Microsoft) <redacted>
---
v2: New patch. Fix teardown race with synchronize_net() for softirq
paths and rtnl_lock() for the process-context genl echo path.
v1: https://lore.kernel.org/netdev/20260816035205.57966-1-blbllhy@gmail.com/ (local)
---
drivers/net/gtp.c | 61 ++++++++++++++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 22 deletions(-)
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index ead519ee18d1..7ac9764696f2 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c@@ -896,12 +896,14 @@ static void gtp_encap_disable_sock(struct sock *sk) static void gtp_encap_disable(struct gtp_dev *gtp) { if (gtp->sk_created) { - udp_tunnel_sock_release(gtp->sk0); - udp_tunnel_sock_release(gtp->sk1u); - /* Pairs with smp_load_acquire() in the RX and - * genl echo paths. + /* Prevent new readers from entering echo handlers, + * then wait for in-flight softirq readers to complete + * before releasing the sockets. */ smp_store_release(>p->sk_created, false); + synchronize_net(); + udp_tunnel_sock_release(gtp->sk0); + udp_tunnel_sock_release(gtp->sk1u); gtp->sk0 = NULL; gtp->sk1u = NULL; } else {
@@ -1473,8 +1475,7 @@ static int gtp_create_sockets(struct gtp_dev *gtp, const struct nlattr *nla, gtp->sk1u = sk1u; /* Ensure sk0/sk1u are visible before sk_created is set. - * Pairs with smp_load_acquire() in the RX and genl - * echo paths. + * Pairs with smp_load_acquire() in the RX echo paths. */ smp_store_release(>p->sk_created, true);
@@ -2362,6 +2363,7 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) struct sock *sk; __be16 port; int len; + int ret; if (!info->attrs[GTPA_VERSION] || !info->attrs[GTPA_LINK] ||
@@ -2373,17 +2375,22 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) dst_ip = nla_get_be32(info->attrs[GTPA_PEER_ADDRESS]); src_ip = nla_get_be32(info->attrs[GTPA_MS_ADDRESS]); + rtnl_lock(); + gtp = gtp_find_dev(sock_net(skb->sk), info->attrs); - if (!gtp) - return -ENODEV; + if (!gtp) { + ret = -ENODEV; + goto out_unlock; + } - /* Pairs with smp_store_release() in gtp_create_sockets() - * and gtp_encap_disable(). - */ - if (!smp_load_acquire(>p->sk_created)) - return -EOPNOTSUPP; - if (!(gtp->dev->flags & IFF_UP)) - return -ENETDOWN; + if (!gtp->sk_created) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + if (!(gtp->dev->flags & IFF_UP)) { + ret = -ENETDOWN; + goto out_unlock; + } if (version == GTP_V0) { struct gtp0_header *gtp0_h;
@@ -2392,8 +2399,10 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) sizeof(struct iphdr) + sizeof(struct udphdr); skb_to_send = netdev_alloc_skb_ip_align(gtp->dev, len); - if (!skb_to_send) - return -ENOMEM; + if (!skb_to_send) { + ret = -ENOMEM; + goto out_unlock; + } sk = gtp->sk0; port = htons(GTP0_PORT);
@@ -2409,8 +2418,10 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) sizeof(struct iphdr) + sizeof(struct udphdr); skb_to_send = netdev_alloc_skb_ip_align(gtp->dev, len); - if (!skb_to_send) - return -ENOMEM; + if (!skb_to_send) { + ret = -ENOMEM; + goto out_unlock; + } sk = gtp->sk1u; port = htons(GTP1U_PORT);
@@ -2420,7 +2431,8 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) memset(gtp1u_h, 0, sizeof(struct gtp1_header_long)); gtp1u_build_echo_msg(gtp1u_h, GTP_ECHO_REQ); } else { - return -ENODEV; + ret = -ENODEV; + goto out_unlock; } rt = ip4_route_output_gtp(&fl4, sk, dst_ip, src_ip);
@@ -2428,7 +2440,8 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) netdev_dbg(gtp->dev, "no route for echo request to %pI4\n", &dst_ip); kfree_skb(skb_to_send); - return -ENODEV; + ret = -ENODEV; + goto out_unlock; } local_bh_disable();
@@ -2442,7 +2455,11 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) dev_net(gtp->dev)), false, 0); local_bh_enable(); - return 0; + ret = 0; + +out_unlock: + rtnl_unlock(); + return ret; } static const struct nla_policy gtp_genl_policy[GTPA_MAX + 1] = {
--
2.55.0