CVE-2026-10639
Description
Use-after-free in Zephyr's ICMPv4 echo reply processing allows remote DoS via ping.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Use-after-free in Zephyr's ICMPv4 echo reply processing allows remote DoS via ping.
Vulnerability
In Zephyr's native IPv4 stack, icmpv4_handle_echo_request() in subsys/net/ip/icmpv4.c builds an echo-reply packet, passes it to net_try_send_data(), and then calls net_stats_update_icmp_sent(net_pkt_iface(reply)). However, net_try_send_data() transfers ownership and may free the packet before the stats update, causing a use-after-free read of net_pkt_iface(reply). This affects versions from v1.14 through v4.4.0 when CONFIG_NET_STATISTICS_ICMP is enabled [1].
Exploitation
An unauthenticated remote attacker can craft a ping (ICMP echo request) to the target device. The vulnerability is reached via net_icmpv4_input and net_icmp_call_ipv4_handlers. The attacker does not require any credentials or special network position. Exploitation depends on a race window where the reply packet is freed before the stats macro reads its interface pointer. The condition is triggered only if CONFIG_NET_STATISTICS_ICMP is enabled [1].
Impact
Successful exploitation leads to a use-after-free read of the reply packet's memory, and if CONFIG_NET_STATISTICS_PER_INTERFACE is set, a write through a stale pointer. This most likely results in corrupted interface statistics or a remotely triggerable crash (denial of service). The effect is probabilistic due to the race condition [1].
Mitigation
The fix caches the interface pointer from the live received packet before sending and uses it for post-send stats updates. The fix was merged in commit 86e2166 [2] and is planned for release in Zephyr 4.5.0. As of this writing, no official release containing the fix is available; users are advised to apply the patch or disable CONFIG_NET_STATISTICS_ICMP as a workaround [1].
AI Insight generated on Jun 16, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: <=4.4.0
Patches
586e21665d464net: ip: icmpv4: fix use-after-free
1 file changed · +11 −9
subsys/net/ip/icmpv4.c+11 −9 modified@@ -429,6 +429,7 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_icmp_ctx *ctx, { struct net_pkt *reply = NULL; struct net_ipv4_hdr *ip_hdr = hdr->ipv4; + struct net_if *iface = net_pkt_iface(pkt); struct net_in_addr req_src, req_dst; const struct net_in_addr *src; struct net_pkt_cursor backup; @@ -459,7 +460,7 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_icmp_ctx *ctx, goto drop; } - reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), + reply = net_pkt_alloc_with_buffer(iface, net_pkt_ipv4_opts_len(pkt) + payload_len, NET_AF_INET, NET_IPPROTO_ICMP, @@ -470,8 +471,8 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_icmp_ctx *ctx, } if (net_ipv4_is_addr_mcast(&req_dst) || - net_ipv4_is_addr_bcast(net_pkt_iface(pkt), &req_dst)) { - src = net_if_ipv4_select_src_addr(net_pkt_iface(pkt), &req_src); + net_ipv4_is_addr_bcast(iface, &req_dst)) { + src = net_if_ipv4_select_src_addr(iface, &req_src); if (net_ipv4_is_addr_unspecified(src)) { NET_DBG("DROP: No src address match"); @@ -511,7 +512,7 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_icmp_ctx *ctx, goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(reply)); + net_stats_update_icmp_sent(iface); net_pkt_cursor_restore(pkt, &backup); return NET_CONTINUE; @@ -520,7 +521,7 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_icmp_ctx *ctx, net_pkt_unref(reply); } - net_stats_update_icmp_drop(net_pkt_iface(pkt)); + net_stats_update_icmp_drop(iface); return NET_DROP; } @@ -529,6 +530,7 @@ int net_icmpv4_send_error(struct net_pkt *orig, uint8_t type, uint8_t code) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr); int err = -EIO; + struct net_if *iface = net_pkt_iface(orig); struct net_ipv4_hdr *ip_hdr; struct net_in_addr orig_src, orig_dst; struct net_pkt *pkt; @@ -558,7 +560,7 @@ int net_icmpv4_send_error(struct net_pkt *orig, uint8_t type, uint8_t code) net_ipv4_addr_copy_raw(orig_src.s4_addr, ip_hdr->src); net_ipv4_addr_copy_raw(orig_dst.s4_addr, ip_hdr->dst); - if (net_ipv4_is_addr_bcast(net_pkt_iface(orig), &orig_dst)) { + if (net_ipv4_is_addr_bcast(iface, &orig_dst)) { /* We should not send an error to packet that * were sent to broadcast */ @@ -578,7 +580,7 @@ int net_icmpv4_send_error(struct net_pkt *orig, uint8_t type, uint8_t code) copy_len = 0; } - pkt = net_pkt_alloc_with_buffer(net_pkt_iface(orig), + pkt = net_pkt_alloc_with_buffer(iface, copy_len + NET_ICMPV4_UNUSED_LEN, NET_AF_INET, NET_IPPROTO_ICMP, PKT_WAIT_TIME); @@ -607,15 +609,15 @@ int net_icmpv4_send_error(struct net_pkt *orig, uint8_t type, uint8_t code) net_sprint_ipv4_addr(&orig_src)); if (net_try_send_data(pkt, K_NO_WAIT) >= 0) { - net_stats_update_icmp_sent(net_pkt_iface(orig)); + net_stats_update_icmp_sent(iface); return 0; } drop: net_pkt_unref(pkt); drop_no_pkt: - net_stats_update_icmp_drop(net_pkt_iface(orig)); + net_stats_update_icmp_drop(iface); return err;
ef293d8b5e5dnet: ipv6: nbr: fix use-after-free
1 file changed · +3 −3
subsys/net/ip/ipv6_nbr.c+3 −3 modified@@ -1135,7 +1135,7 @@ int net_ipv6_send_na(struct net_if *iface, const struct in6_addr *src, goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2063,7 +2063,7 @@ int net_ipv6_send_ns(struct net_if *iface, net_ipv6_nbr_unlock(); - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2135,7 +2135,7 @@ int net_ipv6_send_rs(struct net_if *iface) goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0;
a96ab665bd57net: ipv6: nbr: fix use-after-free
1 file changed · +3 −3
subsys/net/ip/ipv6_nbr.c+3 −3 modified@@ -1222,7 +1222,7 @@ int net_ipv6_send_na(struct net_if *iface, const struct net_in6_addr *src, goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2158,7 +2158,7 @@ int net_ipv6_send_ns(struct net_if *iface, net_ipv6_nbr_unlock(); - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2230,7 +2230,7 @@ int net_ipv6_send_rs(struct net_if *iface) goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0;
aaed8332a62bnet: ipv6: nbr: fix use-after-free
1 file changed · +3 −3
subsys/net/ip/ipv6_nbr.c+3 −3 modified@@ -1222,7 +1222,7 @@ int net_ipv6_send_na(struct net_if *iface, const struct net_in6_addr *src, goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2177,7 +2177,7 @@ int net_ipv6_send_ns(struct net_if *iface, net_ipv6_nbr_unlock(); - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2249,7 +2249,7 @@ int net_ipv6_send_rs(struct net_if *iface) goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0;
3cab8170460fnet: ipv6: nbr: fix use-after-free
1 file changed · +3 −3
subsys/net/ip/ipv6_nbr.c+3 −3 modified@@ -1221,7 +1221,7 @@ int net_ipv6_send_na(struct net_if *iface, const struct in6_addr *src, goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2169,7 +2169,7 @@ int net_ipv6_send_ns(struct net_if *iface, net_ipv6_nbr_unlock(); - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0; @@ -2241,7 +2241,7 @@ int net_ipv6_send_rs(struct net_if *iface) goto drop; } - net_stats_update_icmp_sent(net_pkt_iface(pkt)); + net_stats_update_icmp_sent(iface); net_stats_update_ipv6_nd_sent(iface); return 0;
Vulnerability mechanics
Root cause
"Use-after-free: `icmpv4_handle_echo_request()` reads `net_pkt_iface(reply)` to update ICMP statistics after `net_try_send_data()` has transferred ownership of the reply packet to the TX path, which may already have freed it."
Attack vector
An unauthenticated remote attacker triggers the bug by sending an ICMPv4 echo request (ping) to the device, which reaches `icmpv4_handle_echo_request` via `net_icmpv4_input` -> `net_icmp_call_ipv4_handlers` [CWE-416]. After the reply packet is sent via `net_try_send_data`, the TX path may free the `net_pkt` before the subsequent call to `net_pkt_iface(reply)`, causing a use-after-free read of packet memory and, when `CONFIG_NET_STATISTICS_PER_INTERFACE` is enabled, a wild-pointer write through the stale interface pointer. The race window is probabilistic; impact is corrupted interface statistics or a remotely triggerable crash (DoS).
Affected code
The vulnerability is in `icmpv4_handle_echo_request()` in `subsys/net/ip/icmpv4.c`. The function calls `net_try_send_data(reply)`, which transfers ownership of the `reply` packet to the TX path, and then reads `net_pkt_iface(reply)` from the now-freed packet on the next line to update ICMP statistics. The same pattern is also fixed in `net_icmpv4_send_error()` and in three IPv6 neighbor-discovery functions in `subsys/net/ip/ipv6_nbr.c` (``[ref_id=1]``).
What the fix does
The fix in commit `86e2166` [patch_id=6189719] caches the network interface pointer (`struct net_if *iface`) from the live received packet (`pkt`) at the top of `icmpv4_handle_echo_request()`, before any send occurs. All subsequent uses of `net_pkt_iface()` — for packet allocation, address checks, and post-send stats updates — are replaced with the cached `iface`. This eliminates the read of `reply->iface` after `net_try_send_data()` has transferred ownership and the TX path may have freed `reply`. The same caching pattern is applied in `net_icmpv4_send_error()` and in three IPv6 neighbor-discovery functions (`net_ipv6_send_na`, `net_ipv6_send_ns`, `net_ipv6_send_rs`) to fix identical use-after-free bugs [patch_id=6189720][patch_id=6189721][patch_id=6189722].
Preconditions
- networkThe ICMP echo handler is reachable over the network (default for devices with native IPv4 stack)
- configCONFIG_NET_STATISTICS_ICMP must be enabled (required to reach the vulnerable code path)
Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.