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:
- Malicious or coerced configuration change. An attacker who can edit
fastnetmon.conf(because they have local shell access, or because they exploited a different vulnerability that gave them write access to the config) sets a largeban_details_records_count. On the next FastNetMon restart, the allocation wraps and the daemon corrupts its own heap as soon as the first packet is written. - Configuration-management mistakes. Ansible playbooks, Chef recipes, and Puppet manifests that compute config values from inventory data sometimes generate huge numbers (e.g., counts from a database query that returned the wrong column). The mistake is silent — the daemon starts, looks healthy, then corrupts memory under load.
- Tutorial transcription errors. Operators copy values from blog posts and forum threads. A typo that adds a digit to the count (e.g.,
ban_details_records_count = 50000000intended as5000000) triggers the overflow.
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
- Lock down config file permissions.
/etc/fastnetmon.confshould be owned by root and readable only by the FastNetMon process user. Non-administrative users should not be able to modify it.chmod 0640 /etc/fastnetmon.confand ownership root:fastnetmon is a reasonable default. - Audit
ban_details_records_countin your existing config. A typical value is 500–5000 (small fixed buffer per banned IP). Values above 1,000,000 are almost certainly a typo or a stale tutorial copy. The integer overflow threshold (~2.8M) is far beyond any realistic value. - Run FastNetMon under a low-privilege user. Heap corruption gives the attacker code execution as the FastNetMon process user. If that user is root, the damage is unconstrained; if it's a dedicated
fastnetmonuser with onlyCAP_NET_RAW, the post-exploitation surface is much narrower. - Use configuration management with validation. If you deploy FastNetMon via Ansible/Chef/Puppet, add a schema validation step that asserts numeric configuration values fall within reasonable ranges. Catching the typo at the config-management layer means it never reaches the daemon.
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
| Date | Event |
|---|---|
| 2026-04-25 | Vulnerability identified during Lorikeet Security source code audit of FastNetMon Community Edition 1.2.9 |
| 2026-04-25 | CVE ID requested from MITRE |
| 2026-04-25 | Vendor (Pavel Odintsov / FastNetMon LTD) notified at the contact published in SECURITY.md |
| 2026-05-22 | CVE-2026-48690 assigned by MITRE |
| TBD | Vendor response |
| TBD | Fix release |
| 2026-05-23 | Lorikeet 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.
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.