CVE-2026-48686: 28-Byte Stack Overflow in FastNetMon BGP NLRI Decoder | Lorikeet Security Skip to main content
Back to Blog

CVE-2026-48686: A 28-Byte Stack Overflow in the FastNetMon BGP NLRI Decoder

Lorikeet Security Team May 23, 2026 10 min read
CVSS 9.8 -- Critical

Stack Buffer Overflow via Unvalidated BGP NLRI Prefix Length — Remote Code Execution

CVE
CVE-2026-48686
CVSS
9.8 (Critical)
CWE
CWE-787 (Out-of-bounds Write), CWE-120 (Buffer Copy without Checking Size)
Affected
FastNetMon Community Edition <= 1.2.9
Component
src/bgp_protocol.cpp, function decode_bgp_subnet_encoding_ipv4_raw(), lines 93-111
Attack Vector
Remote (via BGP peer through GoBGP)
Discovered by
Lorikeet Security

This is the bug we worried about when we started reading the FastNetMon BGP code. The function decode_bgp_subnet_encoding_ipv4_raw() in src/bgp_protocol.cpp reads a prefix bit length from a BGP NLRI byte stream, derives a byte count from it, and then performs a memcpy using that byte count into a stack variable that has room for exactly four bytes. There is no bounds check between "I read a byte from the wire" and "I memcpy that many bytes onto my stack."

The math is precise. For IPv4 NLRI, the prefix bit length must be in [0, 32]. If the attacker sets it to 255, the helper how_much_bytes_we_need_for_storing_certain_subnet_mask() computes ceil(255 / 8) = 32. The destination variable is uint32_t prefix_ipv4, which is exactly 4 bytes on the stack. The memcpy(&prefix_ipv4, value + 1, 32) writes 32 bytes into a 4-byte slot, overflowing 28 bytes past the variable.

This is a remote code execution primitive. The default FastNetMon CMake build has zero security flags: no -fstack-protector, no -D_FORTIFY_SOURCE=2, no -fPIE/-pie, no RELRO. There is no stack canary to detect the overflow, no FORTIFY-instrumented memcpy to refuse the oversize copy, and the binary itself is at a static base. 28 bytes is more than enough to overwrite the saved return address with an attacker-controlled value.


The vulnerable code

// src/bgp_protocol.cpp around line 93
bool decode_bgp_subnet_encoding_ipv4_raw(const uint8_t* value, ...) {
    uint8_t prefix_bit_length = value[0];
    // ^^^ Attacker can set this to anything in [0, 255]. No validation.

    uint32_t prefix_byte_length =
        how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length);
    // For prefix_bit_length = 255, returns ceil(255 / 8) = 32.

    uint32_t prefix_ipv4 = 0;          // 4 bytes on the stack
    memcpy(&prefix_ipv4, value + 1, prefix_byte_length);
    // ^^^ Writes up to 32 bytes into a 4-byte variable.
    //     Overflow is exactly prefix_byte_length - 4 bytes (up to 28 bytes).
    //     Source bytes come directly from the attacker.

    convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length, ...);
    // ^^^ Secondary issue: shift of (32 - cidr) for cidr > 32 is undefined behavior.
}

Two distinct problems live in this function. The headline issue is the stack overflow. The secondary issue is that convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length, ...) computes 32 - cidr and then shifts by that amount. For cidr > 32, the subtraction wraps in unsigned arithmetic (producing a huge value), and the subsequent shift is undefined behavior per the C++ standard. In practice on x86, the shift count is masked to 5 bits, but on other architectures the behavior is genuinely undefined and could produce additional memory corruption.


Exploit picture

The 28-byte overflow lands in the stack frame of decode_bgp_subnet_encoding_ipv4_raw(). On x86_64 Linux with SysV ABI, the immediate targets in that frame are:

28 bytes of attacker-controlled write past a 4-byte variable comfortably reaches the saved return address. Because the binary is built without -fPIE/-pie, the text section is at a static base; the attacker can hardcode addresses of gadgets in the FastNetMon binary or libc (libc is usually ASLR-randomized, but the FastNetMon text is not). The combination of "no stack canary" + "no PIE" + "attacker controls saved return address" is the textbook exploitable stack overflow.

The values written are the bytes that follow the prefix_bit_length byte in the NLRI — those are entirely attacker-controlled because they were originally the prefix bytes themselves. The attacker can write any 32-byte payload by encoding it as a BGP NLRI with prefix_bit_length = 255 followed by 32 chosen bytes.


Reachability

Like CVE-2026-48685, this bug is reached through the external BGP daemon (GoBGP or ExaBGP) that FastNetMon receives BGP data from. The attacker has to be a BGP peer of that daemon. In practice, this means:

The "Critical" CVSS rating reflects that, given a viable BGP peering position, an attacker can achieve unauthenticated remote code execution as the FastNetMon process user on the host. FastNetMon typically runs with elevated privileges for raw packet capture, so the post-exploitation impact is high.


How a fix should look

bool decode_bgp_subnet_encoding_ipv4_raw(const uint8_t* value, ...) {
    uint8_t prefix_bit_length = value[0];

    // Validate before doing anything else.
    if (prefix_bit_length > 32) {
        return false;                  // IPv4 prefix length cannot exceed 32 bits.
    }

    uint32_t prefix_byte_length =
        how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length);
    // After the <= 32 check, this is guaranteed to be in [0, 4].

    uint32_t prefix_ipv4 = 0;
    memcpy(&prefix_ipv4, value + 1, prefix_byte_length);
    // Safe: prefix_byte_length is now in [0, 4] which fits in uint32_t.

    convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length, ...);
    // Safe: cidr is now <= 32.
}

The fix is one line. The conditional should be at the top of the function, before any memory is touched, and should be a strict > 32 check, not >= 32 (a /32 host route is a legitimate value).

The structural improvement is to never use memcpy into a fixed-size stack variable with a length that came from the wire. The destination should always have a static size, the source should be bounded by that size, and the copy length should be std::min(static_dest_size, wire_length). That pattern eliminates the entire class of "stack overflow from wire-supplied length" bugs.


The bigger problem: the build has no hardening

The reason this is a critical and not a high is the lack of standard compiler hardening in the default FastNetMon build. Modern compilers offer four cheap, well-tested mitigations that would significantly raise the cost of exploiting a stack overflow:

MitigationWhat it doesFastNetMon default build
-fstack-protector-strongInserts a stack canary before the saved return address. Detects most stack overflows at function return.Not enabled
-D_FORTIFY_SOURCE=2Compile-time and run-time checks for memcpy, strcpy, sprintf, etc. with known destination sizes. Aborts on overflow.Not enabled
-fPIE / -piePosition-independent executable. Makes the text section ASLR-eligible. Defeats hardcoded gadget addresses.Not enabled
-Wl,-z,relro -Wl,-z,nowMakes the GOT/PLT read-only after relocation. Stops common ret2plt exploitation patterns.Not enabled

Adding these flags is two lines in CMakeLists.txt. They cost essentially nothing at runtime. The fact that none of them are enabled means a 28-byte stack overflow goes from "would have been caught by the stack canary and the process would have aborted" to "directly exploitable for code execution." Operators who maintain their own packaging should add these flags to their build before any of the CVEs in this series are even patched.


Compensating controls


The lesson: validate the bit length, every time

Every protocol that encodes IP prefixes as "prefix length followed by N bytes of prefix data" has produced this bug at least once. RFC 4271 specifies the bit-length encoding for BGP NLRI. RFC 5101 specifies it for IPFIX. RFC 4760 specifies it for MP-BGP NLRI families. In every case, the bit length must be validated against the address family's maximum (32 for IPv4, 128 for IPv6) before any byte-count derivation. This is the single most well-known footgun in IP-prefix wire-format parsing, and it produces an exploitable bug every time it gets missed.

If you maintain a BGP parser, an IPFIX collector, a SDN-controller route processor, or any code that converts a bit length to a byte count for a stack copy, this is the line you want highlighted in red in your code review checklist. The cost of the bounds check is zero. The cost of missing it, in this case, is unauthenticated remote code execution as root.


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-48686 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!