Zephyr network stack calls icmpv6_handle_echo_request on IPv4 packet with ICMP type 128 (Echo Request type for ICMPv6). This later leads to reinterpretation of IPv4 header as IPv6 header and enables out of bound memory read. It originates from the way ICMP handlers are registered inside Zephyr. Potential possibility of leaking OOB read on network.
Zephyr version: 7823374e872 release: Zephyr 4.1.0
Build with: `west -v build --pristine -b nucleo_h753zi -d stm32h753_echo_server_stock samples/net/sockets/echo_server -- -DOVERLAY_CONFIG="prj.conf"
Additional Details:
-
Start with a minimal IPv4 header (20 Bytes) encapsulating minimal ICMP packet with type 128, code 0 and correct checksums.
-
When the packet is received and processed by the network stack, it eventually calls net_icmp_call_ipv4_handlers:
// from subsys/net/ip/icmpv4.c
enum net_verdict net_icmpv4_input(struct net_pkt *pkt,
struct net_ipv4_hdr *ip_hdr)
{
...
ret = net_icmp_call_ipv4_handlers(pkt, ip_hdr, icmp_hdr);
...
}
net_icmp_call_ipv4_handlers then calls icmp_call_handlers which iterates through handlers and finds the corresponding handler function if there is a match.
// from subsys/net/ip/icmp.c
static int icmp_call_handlers(struct net_pkt *pkt,
struct net_icmp_ip_hdr *ip_hdr,
struct net_icmp_hdr *icmp_hdr)
{
...
SYS_SLIST_FOR_EACH_CONTAINER(&handlers, ctx, node) {
if (ctx->type == icmp_hdr->type &&
(ctx->code == icmp_hdr->code || ctx->code == 0U)) {
...
ret = ctx->handler(ctx, pkt, ip_hdr, icmp_hdr, ctx->user_data);
...
}
}
...
}
- The problem arises because icmp.c uses a common
static sys_slist_t handlers = SYS_SLIST_STATIC_INIT(&handlers); for both ICMPv4 and ICMPv6. Later Zephyr uses net_icmp_init_ctx to register handlers for different types.
// from subsys/net/ip/icmpv4.c
ret = net_icmp_init_ctx(&ctx, NET_ICMPV4_ECHO_REQUEST, 0, icmpv4_handle_echo_request);
// from subsys/net/ip/icmpv6.c
ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
Because of this there is a handler for type 128 even for ICMPv4 packets which should not exist.
- As a result we get a backtrace like:
+bt
#0 icmpv6_handle_echo_request (ctx=0x2401a558 <ctx>, pkt=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, hdr=0x24000640 <eth0_data+1600>,
icmp_hdr=0x24034ebe <net_buf_data_rx_bufs+202>, user_data=0x0)
at zephyr/subsys/net/ip/icmpv6.c:117
#1 0x0803499e in icmp_call_handlers (pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, ip_hdr=ip_hdr@entry=0x24000640 <eth0_data+1600>,
icmp_hdr=icmp_hdr@entry=0x24034ebe <net_buf_data_rx_bufs+202>) at zephyr/subsys/net/ip/icmp.c:523
#2 0x0803529e in net_icmp_call_ipv4_handlers (pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, ipv4_hdr=ipv4_hdr@entry=0x24034e82 <net_buf_data_rx_bufs+142>,
icmp_hdr=icmp_hdr@entry=0x24034ebe <net_buf_data_rx_bufs+202>) at zephyr/subsys/net/ip/icmp.c:546
#3 0x08037840 in net_icmpv4_input (pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, ip_hdr=ip_hdr@entry=0x24034e82 <net_buf_data_rx_bufs+142>)
at zephyr/subsys/net/ip/icmpv4.c:643
#4 0x08038798 in net_ipv4_input (pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, is_loopback=is_loopback@entry=false)
at zephyr/subsys/net/ip/ipv4.c:388
#5 0x08022f72 in process_data (is_loopback=false, pkt=0x24037adc <_k_mem_slab_buf_rx_pkts+168>)
at zephyr/subsys/net/ip/net_core.c:156
#6 processing_data (pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, is_loopback=is_loopback@entry=false) at zephyr/subsys/net/ip/net_core.c:174
#7 0x080235f6 in net_rx (pkt=0x24037adc <_k_mem_slab_buf_rx_pkts+168>, iface=<optimized out>) at zephyr/subsys/net/ip/net_core.c:467
#8 0x08023982 in net_queue_rx (iface=<optimized out>, pkt=0x24037adc <_k_mem_slab_buf_rx_pkts+168>) at zephyr/subsys/net/ip/net_core.c:494
#9 net_recv_data (iface=<optimized out>, pkt=pkt@entry=0x24037adc <_k_mem_slab_buf_rx_pkts+168>) at zephyr/subsys/net/ip/net_core.c:604
#10 0x08054034 in rx_thread (arg1=0x80659a8 <__device_dts_ord_118>, unused1=<optimized out>, unused2=<optimized out>) at zephyr/drivers/ethernet/eth_stm32_hal.c:736
#11 0x0800642e in z_thread_entry (entry=0x8053d41 <rx_thread>, p1=0x80659a8 <__device_dts_ord_118>, p2=0x0, p3=0x0) at zephyr/lib/os/thread_entry.c:48
#12 0xaaaaaaaa in ?? ()
Where #0 icmpv6_handle_echo_request gets called from #4 net_ipv4_input. Consequently, IPv4 header gets reinterpreted as IPv6 header inside icmpv6_handle_echo_request which can lead to out of bounds read (refer to length difference between IPv4 header and IPv6).
// from subsys/net/ip/icmpv6.c
static int icmpv6_handle_echo_request(struct net_icmp_ctx *ctx,
struct net_pkt *pkt,
struct net_icmp_ip_hdr *hdr,
struct net_icmp_hdr *icmp_hdr,
void *user_data)
{
...
struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
...
}
-
It may also be possible to read much more out of bounds and send out on network because of reinterpreted length field inside the new IPv6 header. But it needs careful crafting of payload and passing through net_pkt_copy. I have not done it but would give it a try next month. Would be cool to leak out significant data in outgoing reply.
-
Overall there are multiple instances of net_icmp_init_ctx in the code and I assume there would be a lot of wrong function calls in the network stack in a similar fashion.
net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
net_icmp_init_ctx(&ctx, NET_ICMPV6_MLD_QUERY, 0, handle_mld_query);
net_icmp_init_ctx(&ns_ctx, NET_ICMPV6_NS, 0, handle_ns_input);
net_icmp_init_ctx(&na_ctx, NET_ICMPV6_NA, 0, handle_na_input);
Patches
main: #98780
v4.2: #98983
v4.1: #98984
v3.7: #98985
For more information
If you have any questions or comments about this advisory:
embargo: 2026-01-28
Zephyr network stack calls
icmpv6_handle_echo_requeston IPv4 packet with ICMP type 128 (Echo Request type for ICMPv6). This later leads to reinterpretation of IPv4 header as IPv6 header and enables out of bound memory read. It originates from the way ICMP handlers are registered inside Zephyr. Potential possibility of leaking OOB read on network.Zephyr version:
7823374e872 release: Zephyr 4.1.0Build with: `west -v build --pristine -b nucleo_h753zi -d stm32h753_echo_server_stock samples/net/sockets/echo_server -- -DOVERLAY_CONFIG="prj.conf"
Additional Details:
Start with a minimal IPv4 header (20 Bytes) encapsulating minimal ICMP packet with type 128, code 0 and correct checksums.
When the packet is received and processed by the network stack, it eventually calls
net_icmp_call_ipv4_handlers:net_icmp_call_ipv4_handlersthen callsicmp_call_handlerswhich iterates throughhandlersand finds the corresponding handler function if there is a match.static sys_slist_t handlers = SYS_SLIST_STATIC_INIT(&handlers);for both ICMPv4 and ICMPv6. Later Zephyr usesnet_icmp_init_ctxto register handlers for different types.Because of this there is a handler for type 128 even for ICMPv4 packets which should not exist.
Where
#0 icmpv6_handle_echo_requestgets called from#4 net_ipv4_input. Consequently, IPv4 header gets reinterpreted as IPv6 header insideicmpv6_handle_echo_requestwhich can lead to out of bounds read (refer to length difference between IPv4 header and IPv6).It may also be possible to read much more out of bounds and send out on network because of reinterpreted
lengthfield inside the new IPv6 header. But it needs careful crafting of payload and passing throughnet_pkt_copy. I have not done it but would give it a try next month. Would be cool to leak out significant data in outgoing reply.Overall there are multiple instances of
net_icmp_init_ctxin the code and I assume there would be a lot of wrong function calls in the network stack in a similar fashion.Patches
main: #98780
v4.2: #98983
v4.1: #98984
v3.7: #98985
For more information
If you have any questions or comments about this advisory:
embargo: 2026-01-28