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 |
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.