โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0494

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_hln to a large value (e.g. 200) for heap leak, or ar_pln large for stack leak.
  • Set ar_op = ARPOP_REQUEST.
  • Set ar_tpa to the victim's IPv4 address.
  • Pad the frame to arphdr_len(ar_hln, ar_pln) bytes.
  • Victim receives, arpintr accepts (valid ar_hrd, valid ar_pro, packet long enough), dispatches to in_arpinput, which hits taddr == myaddr โ†’ goto reply โ†’ in_arpreply โ†’ OOB memcpy โ†’ 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_dl per request (heap variant), and up to ~251 bytes of stack per request (stack variant via ar_pln).
  • KASLR bypass: leaked heap frequently contains kernel text/data pointers from adjacent allocations (function pointers, ifaddr back-pointers, etc.).
  • Key material exposure: if a crypto buffer or key cache allocation happens to be adjacent to the sockaddr_dl in 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.

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() validates ar_hln == ifp->if_addrlen at ingress
  • NetBSD in_arpinput() validates ar_hln against ifp->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).