Integer Overflow in Packet Reassembly: How One Missing Check Enables Heap Corruption
Introduction
Network packet parsing is one of the most attack-exposed surfaces in any networked application. Every byte that arrives from the network is potentially attacker-controlled — and when your code trusts those bytes without validation, the consequences can be severe.
This post examines a critical heap buffer overflow vulnerability found in a network packet reassembly function written in C. The root cause is deceptively simple: an integer overflow in a length accumulation loop that goes unchecked before being passed to malloc(). The result? An attacker can send a single malformed packet and corrupt heap memory.
Whether you're a seasoned systems programmer or a developer just getting started with C networking code, this vulnerability illustrates a class of bugs that continues to haunt real-world software — and the fix is a masterclass in defensive programming.
The Vulnerability Explained
What Is Packet Reassembly?
In many network protocols, large messages are split across multiple packets. A reassembly function collects these fragments and stitches them back together into a single contiguous buffer. To do this, it typically:
- Iterates over a list of received packet fragments
- Sums up the total payload length
- Allocates a buffer of that total size
- Copies each fragment's payload into the buffer
This is exactly what merge_packet() does in net_channel_ex.c. And step 2 — summing the lengths — is where the vulnerability lives.
The Vulnerable Code
Here's the relevant portion of the original code:
static unsigned char* merge_packet(List_t* list, unsigned int* mergelen) {
// ...
unsigned int off = 0;
for (cur = list->head; cur; cur = next) {
packet = pod_container_of(cur, NetReactorPacket_t, _.node);
next = cur->next;
off += packet->_.bodylen; // ← No overflow check!
}
ptr = (unsigned char*)malloc(off); // ← Allocates potentially tiny buffer
// ... then copies full data into ptr
}
The variable off is an unsigned int. Each iteration adds packet->_.bodylen — a value that comes directly from the packet header, meaning it is fully attacker-controlled.
The Integer Overflow
Here's the critical insight: unsigned integer arithmetic in C wraps around silently.
Imagine an attacker crafts two packets:
- Packet 1: bodylen = 0xFFFFFFF0 (4,294,967,280)
- Packet 2: bodylen = 0x00000020 (32)
When these are added:
0xFFFFFFF0 + 0x00000020 = 0x100000010
But since off is a 32-bit unsigned int, the result wraps around to:
0x00000010 (just 16 bytes!)
Now malloc(16) allocates a tiny 16-byte buffer, but the subsequent copy loop faithfully copies gigabytes of data into it — overflowing far beyond the allocated region and corrupting whatever lives next to it on the heap.
Real-World Impact
This vulnerability can be exploited by anyone who can send network packets to the target process. The impact includes:
- Heap corruption: Overwriting adjacent heap metadata or application data
- Remote Code Execution (RCE): With careful heap manipulation, an attacker may control what gets overwritten and redirect execution flow
- Denial of Service (DoS): Even without achieving RCE, heap corruption typically causes a crash
- No authentication required: A single malformed packet is sufficient — no session or login needed
This maps to CWE-190: Integer Overflow or Wraparound and CWE-122: Heap-Based Buffer Overflow, and is consistent with vulnerabilities scored at the highest severity levels under CVSS.
Attack Scenario
Attacker Server
| |
|-- Packet 1 (bodylen=0xFFFFFFF0) -->|
|-- Packet 2 (bodylen=0x00000020) -->|
| |
| merge_packet() called
| off = 0x10 (16 bytes, wrapped!)
| malloc(16) → tiny buffer allocated
| memcpy copies 4GB+ of data
| HEAP CORRUPTED 💥
| |
The Fix
What Changed
The fix adds a single, surgical check inside the accumulation loop:
for (cur = list->head; cur; cur = next) {
packet = pod_container_of(cur, NetReactorPacket_t, _.node);
next = cur->next;
if (off + packet->_.bodylen < off) { // ← Overflow detection
return NULL;
}
off += packet->_.bodylen;
}
Here's the full diff:
for (cur = list->head; cur; cur = next) {
packet = pod_container_of(cur, NetReactorPacket_t, _.node);
next = cur->next;
+ if (off + packet->_.bodylen < off) {
+ return NULL;
+ }
off += packet->_.bodylen;
}
Why This Works
The check off + packet->_.bodylen < off exploits a fundamental property of unsigned integer arithmetic:
If adding a positive value to an unsigned integer produces a result smaller than the original, an overflow has occurred.
In standard C, unsigned integer overflow is well-defined (it wraps modulo 2^N), so this check is both portable and correct. If the addition would wrap, the function immediately returns NULL, signaling to the caller that the packet list is malformed.
This is a classic and idiomatic overflow guard in C, sometimes called a pre-addition overflow check. It's simple, has zero performance overhead, and is immediately understandable to any C developer.
Before vs. After
| Scenario | Before Fix | After Fix |
|---|---|---|
| Normal packets | ✅ Works correctly | ✅ Works correctly |
Single oversized bodylen |
❌ Heap overflow | ✅ Returns NULL safely |
| Multiple packets that overflow when summed | ❌ Heap overflow | ✅ Returns NULL safely |
| Attacker-crafted wraparound | ❌ RCE possible | ✅ Safely rejected |
Prevention & Best Practices
1. Always Validate Attacker-Controlled Length Fields
Any length, size, or count value that originates from a network packet, file, or external input is untrusted by definition. Before using such values in arithmetic or memory allocation, validate them:
// Bad: trust the network
size_t total = header->length;
char* buf = malloc(total);
// Good: validate before use
if (header->length == 0 || header->length > MAX_ALLOWED_SIZE) {
return ERROR_INVALID_LENGTH;
}
char* buf = malloc(header->length);
2. Use Safe Integer Arithmetic Helpers
For C/C++ projects, consider using helper functions or compiler built-ins for overflow-safe arithmetic:
// GCC/Clang built-in: returns true if overflow would occur
unsigned int result;
if (__builtin_uadd_overflow(off, bodylen, &result)) {
return NULL;
}
off = result;
For C++, the <numeric> utilities or libraries like SafeInt provide robust overflow-safe operations.
3. Apply Defense in Depth with Maximum Size Limits
Even with overflow checks, it's wise to enforce an upper bound on total reassembled size:
#define MAX_REASSEMBLY_SIZE (64 * 1024 * 1024) // 64 MB cap
if (off + packet->_.bodylen < off ||
off + packet->_.bodylen > MAX_REASSEMBLY_SIZE) {
return NULL;
}
This limits the blast radius even if a future bug bypasses the overflow check.
4. Prefer Memory-Safe Languages Where Possible
Languages like Rust make this class of bug impossible by default — integer overflows cause a panic in debug mode and use wrapping semantics in release mode, with explicit opt-in required for either behavior. For new network-facing code, consider whether Rust or another memory-safe language is appropriate.
5. Use Static Analysis and Fuzzing
- Static analyzers like
clang-tidy,Coverity, orCodeQLcan flag unchecked arithmetic on attacker-controlled values - Fuzzers like AFL++ or libFuzzer are exceptionally good at finding this class of bug — feed them malformed packet streams and let them find the edge cases
- AddressSanitizer (ASan) will catch the heap overflow at runtime during testing
6. Reference Security Standards
This vulnerability class is well-documented:
- CWE-190: Integer Overflow or Wraparound
- CWE-122: Heap-Based Buffer Overflow
- OWASP: Buffer Overflow
- CERT C: INT30-C. Ensure that unsigned integer operations do not wrap
Conclusion
This vulnerability is a perfect example of how a single missing check in network-facing C code can open the door to remote code execution. The merge_packet() function trusted attacker-supplied length values, accumulated them without overflow protection, and passed the result directly to malloc() — a textbook recipe for heap corruption.
The fix is just three lines, but those three lines close a critical attack vector entirely. This is the essence of secure coding in C: every arithmetic operation on untrusted data is a potential vulnerability, and the cost of a guard check is always worth the security guarantee it provides.
Key Takeaways
- 🔢 Unsigned integer overflow is silent and well-defined in C — it wraps, and attackers know how to exploit that
- 📦 Packet header fields are attacker-controlled — never trust them without validation
- 🛡️ Pre-addition overflow checks (
if (a + b < a)) are idiomatic, correct, and cheap - 🔍 Fuzz your packet parsers — this is exactly the kind of bug that fuzzers find quickly
- 📏 Enforce maximum sizes as a second layer of defense
Security is built one careful check at a time. The next time you write a loop that accumulates lengths from external data, remember this bug — and add the guard.
This vulnerability was identified and fixed automatically by OrbisAI Security. Automated security scanning helps catch these issues before they reach production.