CVE-2026-48690: FastNetMon Packet Capture Buffer Has a 32-bit Integer Overflow | Lorikeet Security Skip to main content
Back to Blog

CVE-2026-48690: FastNetMon Packet Capture Buffer Has a 32-bit Integer Overflow

Lorikeet Security Team May 23, 2026 7 min read
CVSS 7.0 -- High

32-bit Multiplication Wraps to a Tiny Allocation — Subsequent Writes Overflow the Heap

CVE
CVE-2026-48690
CVSS
7.0 (High)
CWE
CWE-190 (Integer Overflow), CWE-122 (Heap-based Buffer Overflow)
Affected
FastNetMon Community Edition <= 1.2.9
Component
src/packet_storage.hpp, function allocate_buffer(), lines 23-25
Attack Vector
Local (configuration file)
Discovered by
Lorikeet Security

FastNetMon's packet-capture subsystem maintains a ring buffer of recently-seen packets, used to assemble "ban details" reports for IPs that get blackholed. The number of packets stored is configurable via the ban_details_records_count directive in fastnetmon.conf. The capture buffer is allocated in one shot at startup with a size proportional to that count.

The allocation math is done in 32-bit unsigned arithmetic. With per-slot size around 1516 bytes (1500-byte payload plus 16-byte pcap-style header), the multiplication overflows at buffer_size_in_packets >= 2,832,542. Above that, the computed allocation size wraps around to a small number, the allocator hands back a tiny chunk, and subsequent write_packet() calls write past the end of that chunk into adjacent heap memory.

Disclosure status: Lorikeet Security notified FastNetMon LTD on April 25, 2026. CVE-2026-48690 was assigned by MITRE. No vendor response or fix as of May 23, 2026.


The vulnerable code

// src/packet_storage.hpp, allocate_buffer(), lines 23-25
bool allocate_buffer(unsigned int buffer_size_in_packets) {
    unsigned int memory_size_in_bytes =
        buffer_size_in_packets * (max_captured_packet_size + sizeof(fastnetmon_pcap_pkthdr_t))
        + sizeof(fastnetmon_pcap_file_header_t);
    // ^^^ 32-bit unsigned multiplication. Wraps at 2^32 bytes (~4.3 GB).
    //     With per-slot = 1516 bytes, wraps at buffer_size_in_packets ~= 2,832,542.

    memory_pointer = (unsigned char*)malloc(memory_size_in_bytes);
    // ^^^ Tiny allocation when the multiplication wrapped.
}

The arithmetic uses unsigned int (32-bit on every platform FastNetMon supports). The result is also stored in unsigned int. So even on 64-bit systems where size_t is 64-bit, the intermediate multiplication is 32-bit and wraps.

The buffer_size_in_packets argument is derived from the ban_details_records_count config value, parsed via atoi(). atoi() does not perform any overflow or range checking; an operator who sets ban_details_records_count = 4000000000 (a typo, a unit confusion, a misread tutorial) gets a wildly-wrong-sized allocation with no warning.


Attack surface: local, configuration-driven

The CVSS attack vector is "Local" because the trigger is a configuration value, not a network packet. The attack scenarios for an integer overflow in a config-driven allocation are:

The exploitation impact is the same as any heap overflow: depending on what allocations land adjacent to the undersized buffer, the attacker can corrupt heap metadata, function pointers, vtables, or other allocator-managed data. The "Local" attack vector limits this CVE's severity, but the exploitation primitive itself is just as serious as the network-triggered overflows elsewhere in this series.


How a fix should look

bool allocate_buffer(unsigned int buffer_size_in_packets) {
    // 1. Sanity-cap the input.
    constexpr unsigned int MAX_REASONABLE_PACKETS = 1'000'000;
    if (buffer_size_in_packets > MAX_REASONABLE_PACKETS) {
        log_error("ban_details_records_count too large: %u (max %u)",
                  buffer_size_in_packets, MAX_REASONABLE_PACKETS);
        return false;
    }

    // 2. Use size_t (64-bit on 64-bit platforms) for the arithmetic.
    size_t per_slot = static_cast<size_t>(max_captured_packet_size)
                    + sizeof(fastnetmon_pcap_pkthdr_t);
    size_t memory_size_in_bytes = static_cast<size_t>(buffer_size_in_packets) * per_slot
                                + sizeof(fastnetmon_pcap_file_header_t);

    // 3. (Optional but cheap) Defensively check for overflow even on 64-bit, since size_t
    //    can also overflow if someone really tries.
    if (per_slot != 0 && buffer_size_in_packets > (SIZE_MAX - sizeof(fastnetmon_pcap_file_header_t)) / per_slot) {
        return false;
    }

    memory_pointer = static_cast<unsigned char*>(malloc(memory_size_in_bytes));
    return memory_pointer != nullptr;
}

Three improvements: (1) a sanity cap rejects obviously-wrong configuration values with a useful log line, (2) the arithmetic uses size_t instead of unsigned int so that 64-bit platforms get 64-bit math, and (3) an explicit overflow check handles the case where someone tries the same attack at a much larger scale on a 64-bit allocation.

The config parser should additionally validate that ban_details_records_count is a positive integer in a sensible range. atoi() should be replaced with std::stoul with explicit range validation, and any out-of-range value should produce a parse error at startup instead of silently being clamped or wrapped.


Compensating controls


The pattern: malloc(n * sz) is a footgun

Every project that does malloc(count * size) without explicit overflow checking has this bug class lurking somewhere. The standard library has carried calloc since 1989 specifically because it performs the multiplication with overflow detection and refuses oversized requests. Most projects don't use it for typed allocations because they prefer the type-cast style of malloc.

Modern C++ has better answers. std::vector<T>::reserve(n) calls std::allocator, which throws std::bad_alloc on overflow. std::make_unique<T[]>(n) similarly traps overflow. Both produce far safer code than raw malloc(n * sizeof(T)) while costing nothing at runtime.

For C code or for C++ code that cannot avoid raw malloc, the defensive idiom is to compute the size in a wider type, check for overflow against the narrow type's maximum, and only then call malloc. The pattern is:

uint64_t bytes = (uint64_t)count * (uint64_t)size;
if (bytes > SIZE_MAX) {
    return nullptr;
}
void* p = malloc((size_t)bytes);

This costs three lines and an extra cast. It eliminates an entire bug class. There's no excuse for not using it in security-relevant allocators.


Disclosure timeline

DateEvent
2026-04-25Vulnerability identified during Lorikeet Security source code audit of FastNetMon Community Edition 1.2.9
2026-04-25CVE ID requested from MITRE
2026-04-25Vendor (Pavel Odintsov / FastNetMon LTD) notified at the contact published in SECURITY.md
2026-05-22CVE-2026-48690 assigned by MITRE
TBDVendor response
TBDFix release
2026-05-23Lorikeet Security publishes responsible disclosure report

Full Responsible Disclosure Report (PDF)

Complete writeup of all 16 FastNetMon Community Edition vulnerabilities Lorikeet Security identified, including vulnerable-code excerpts, impact analysis, and remediation guidance for each CVE.

Download PDF

Auditing the network infrastructure your business depends on

Lorikeet Security finds the parser bugs, protocol confusion, and unauthenticated-control-plane issues that move your DDoS detector, your BGP speaker, and your edge devices from "running" to "exploitable." Source code review, fuzz harness development, and adversarial protocol testing.

-- views
Link copied!
Lorikeet Security

Lorikeet Security Team

Penetration Testing & Cybersecurity Consulting

Lorikeet Security helps modern engineering teams ship safer software. Our work spans web applications, APIs, cloud infrastructure, and AI-generated codebases — and everything we publish here comes from patterns we see in real client engagements.

Lory waving

Hi, I'm Lory! Need help finding the right service? Click to chat!