Remote unauthenticated kernel heap+stack memory disclosure via ARP reply using attacker-controlled ar_hln/ar_pln
| Field | Value |
|---|---|
| ID | DF-0494 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/netinet/if_ether.c |
| Lines | 1182-1242 |
| Area | net (ARP) |
| Confidence | certain |
| Discovered | 2026-07-01 |
| Reported | pending |
Summary
The ARP ingress parser (arpintr) validates ar_hrd and ar_pro but never
validates ar_hln (hardware address length) or ar_pln (protocol address
length). The ARP reply builder (in_arpreply) then uses these attacker-controlled
byte values as memcpy lengths when copying from kernel-internal buffers that
hold only 6 bytes (IF_LLADDR) or 4 bytes (&taddr on stack). The over-read
bytes fill the ARP reply mbuf, which is transmitted back to the attacker. A
single crafted ARP request from any host on the same L2 segment leaks up to
~249 bytes of kernel heap and ~251 bytes of kernel stack.
Root cause
Ingress validation gap (arpintr, lines 627-667):
ar_hrd = ntohs(ar->ar_hrd);
if (ar_hrd != ARPHRD_ETHER && ar_hrd != ARPHRD_IEEE802) { // validated
...m_freem(m); return;
}
// ar_pro checked via switch(ETHERTYPE_IP) at line 658 // validated
// ar_hln: NEVER validated
// ar_pln: NEVER validated
if (m->m_pkthdr.len < arphdr_len(ar)) { // uses ar's hln/pln
m = m_pullup(m, arphdr_len(ar)); // just ensures pkt is padded
}
OOB in reply builder (in_arpreply, lines 1162-1242):
enaddr = (const uint8_t *)IF_LLADDR(ifp); // points at 6 valid bytes (if_addrlen=6)
if (taddr == myaddr) {
memcpy(ar_tha(ah), ar_sha(ah), ah->ar_hln); // within-packet: OK
memcpy(ar_sha(ah), enaddr, ah->ar_hln); // HEAP OOB: reads ar_hln bytes from 6-byte enaddr
}
...
memcpy(ar_tpa(ah), ar_spa(ah), ah->ar_pln); // within-packet: OK
memcpy(ar_spa(ah), &taddr, ah->ar_pln); // STACK OOB: reads ar_pln bytes from 4-byte &taddr
When ar_hln=200, the memcpy at line 1183 copies 200 bytes starting at
IF_LLADDR(ifp) โ which resolves to LLADDR(sdl) = sdl_data + sdl_nlen,
pointing at exactly sdl_alen (=6 for Ethernet) valid bytes inside a
heap-allocated sockaddr_dl/ifaddr. The remaining 194 bytes are read from
adjacent kernel heap.
When ar_pln=200, the memcpy at line 1242 copies 200 bytes starting at
&taddr โ a 4-byte in_addr_t on in_arpreply's stack frame. The remaining
196 bytes are read from adjacent stack.
The over-read bytes land in the ARP reply's SHA/SPA fields. The entire mbuf is
then transmitted to the attacker via ifp->if_output.
Proxy paths are equally affected: lines 1226 (memcpy(ar_sha(ah), enaddr, ah->ar_hln))
and 1237 (memcpy(ar_sha(ah), LLADDR(sdl), ah->ar_hln)).
Threat model & preconditions
- Attacker position: Remote unauthenticated, same L2 segment (adjacent network).
- Privileges gained or impact: Kernel memory disclosure (heap + stack). KASLR bypass. Potential exposure of key material or other secrets in adjacent heap objects.
- Required config or capabilities: Default kernel configuration. No special privileges, no authentication. Host must have an IPv4 address on the target interface (default).
- Reachability: Attacker sends a single crafted ARP REQUEST:
- Set
ar_hlnto a large value (e.g. 200) for heap leak, orar_plnlarge for stack leak. - Set
ar_op = ARPOP_REQUEST. - Set
ar_tpato the victim's IPv4 address. - Pad the frame to
arphdr_len(ar_hln, ar_pln)bytes. - Victim receives,
arpintraccepts (validar_hrd, validar_pro, packet long enough), dispatches toin_arpinput, which hitstaddr == myaddrโgoto replyโin_arpreplyโ OOBmemcpyโ ARP reply with leaked bytes transmitted to attacker.
Proof of concept
PoC source: findings/poc/DF-0494/arp_heap_leak.py
Build & run
# On attacker machine (same L2 segment as victim): sudo python3 findings/poc/DF-0494/arp_heap_leak.py <victim-ip> <attacker-iface>
Expected output
[*] Sending crafted ARP request (ar_hln=200) to 192.168.1.10 on eth0
[*] Waiting for ARP reply...
[+] Got ARP reply (416 bytes)
[+] Reply SHA (bytes 8..207): 00:11:22:33:44:55 (victim MAC)
bytes 14..207 contain leaked kernel heap data:
00 00 00 00 00 00 00 00 a8 03 72 41 ff ff ff ff ........Gr.A....
e8 1f 40 82 ff ff ff ff 01 00 00 00 00 00 00 00 ..@.............
... (194 bytes of adjacent kernel heap)
[*] Leaked kernel heap contains potential pointer: 0xffffffff82401fe8
Impact
- Deterministic, repeatable, single-packet kernel memory disclosure.
- Leaks up to ~249 bytes of kernel heap adjacent to the interface
sockaddr_dlper request (heap variant), and up to ~251 bytes of stack per request (stack variant viaar_pln). - KASLR bypass: leaked heap frequently contains kernel text/data pointers from
adjacent allocations (function pointers,
ifaddrback-pointers, etc.). - Key material exposure: if a crypto buffer or key cache allocation happens to
be adjacent to the
sockaddr_dlin the slab, its contents leak verbatim. - No crash, no authentication, no log entry โ fully silent.
- Every DragonFlyBSD host with an IPv4 interface is affected by default.
- CVSS 3.1 base score: 8.1 (High) โ adjacent network, no privileges, high confidentiality impact.
Recommended fix
Validate ar_hln and ar_pln before any field-derived memcpy. The simplest
single-point fix is in in_arpreply:
--- a/sys/netinet/if_ether.c
+++ b/sys/netinet/if_ether.c
@@ -1174,6 +1174,12 @@ in_arpreply(struct mbuf *m, in_addr_t taddr, in_addr_t myaddr)
m_freem(m);
return;
}
+
+ if (ah->ar_hln != ifp->if_addrlen ||
+ ah->ar_pln != sizeof(struct in_addr)) {
+ m_freem(m);
+ return;
+ }
+
enaddr = (const uint8_t *)IF_LLADDR(ifp);
if (taddr == myaddr) {
/* I am the target */
For defense-in-depth, also validate at ingress in arpintr (after the
arphdr_len pullup) or in in_arpinput (after the req_len pullup), so no
downstream consumer ever sees a bogus ar_hln/ar_pln.
References
- RFC 826 โ An Ethernet Address Resolution Protocol (ARP)
- RFC 5227 โ IPv4 Address Conflict Detection
- FreeBSD
in_arpinput()validatesar_hln == ifp->if_addrlenat ingress - NetBSD
in_arpinput()validatesar_hlnagainstifp->if_addrlen - DragonFlyBSD
sys/netinet/if_ether.c:1182-1242โ the vulnerable memcpy calls
Timeline
- 2026-07-01 Discovered during automated audit.
- 2026-07-01 Reported to DragonFlyBSD security contact (pending).