Back to Blog
critical SEVERITY8 min read

Critical DHCP Heap Overflow: How a Missing Bounds Check Opens the Door to Memory Corruption

A critical heap buffer overflow vulnerability was discovered in a DHCP server implementation where the hardware address length field (`hlen`) from an attacker-controlled packet was trusted without validation, allowing up to 239 bytes of heap corruption. The fix adds a simple bounds check before the memory copy, ensuring the copy length never exceeds the destination buffer size. This type of vulnerability can lead to remote code execution, denial of service, or full system compromise in network-f

O
By Orbis AppSec
Published May 28, 2026Reviewed June 3, 2026

Answer Summary

This is a heap buffer overflow vulnerability (CWE-122) in a C-based DHCP server where the hardware address length field (`hlen`) from incoming packets was trusted without bounds checking before being used in a memory copy operation. An attacker could send a DHCP packet with an `hlen` value up to 239 bytes, causing heap corruption beyond the destination buffer's capacity. The fix adds a simple bounds check ensuring the copy length never exceeds the destination buffer size, preventing memory corruption and potential remote code execution.

Vulnerability at a Glance

cweCWE-122 (Heap-based Buffer Overflow)
fixAdd bounds check to ensure copy length never exceeds destination buffer size
riskRemote code execution, denial of service, or full system compromise through malicious DHCP packets
languageC
root causeTrusting attacker-controlled `hlen` field from DHCP packets without validation before memory copy
vulnerabilityHeap Buffer Overflow via Unchecked DHCP Hardware Address Length

Critical DHCP Heap Overflow: How a Missing Bounds Check Opens the Door to Memory Corruption

Introduction

Network protocols are the backbone of modern connectivity, but they also represent one of the most dangerous attack surfaces in any system. When a network-facing service trusts data from an untrusted source without validation, the consequences can be catastrophic. This post examines a critical heap buffer overflow discovered in a DHCP server implementation — a vulnerability that required just one extra line of code to fix, but could have allowed an attacker to corrupt memory and potentially execute arbitrary code.

If you write C code that handles network packets, processes binary protocols, or copies data from external sources, this vulnerability is a textbook example of what can go wrong when you trust attacker-controlled length fields.


The Vulnerability Explained

What Is a Heap Buffer Overflow?

A buffer overflow occurs when a program writes more data into a buffer than the buffer was allocated to hold. When this happens on the heap (dynamically allocated memory), an attacker can overwrite adjacent heap structures — potentially including function pointers, metadata used by the allocator, or other sensitive data. In the worst case, this leads to arbitrary code execution.

The Vulnerable Code

The vulnerability lived in components/net/lwip-dhcpd/dhcp_server_raw.c, inside the dhcp_client_alloc function:

// VULNERABLE CODE (before fix)
SMEMCPY(node->chaddr, msg->chaddr, msg->hlen);

At first glance, this looks innocent — it's copying the client hardware address (chaddr) from an incoming DHCP packet into a newly allocated node structure. But there's a critical problem: msg->hlen comes directly from the attacker-controlled DHCP packet and is never validated.

Understanding the DHCP Packet Structure

In the DHCP protocol (defined in RFC 2131), a DHCP message contains:

  • chaddr: The client hardware address field — 16 bytes maximum for Ethernet addresses
  • hlen: The hardware address length — a single byte that can hold values from 0 to 255

The destination buffer node->chaddr is sized for the maximum legitimate hardware address: 16 bytes. But hlen is a raw byte from the network packet. An attacker can set it to 255 — the maximum value a single byte can hold.

Here's what happens:

Destination buffer size:  16 bytes  (sized for max Ethernet HW address)
Attacker-supplied hlen:  255 bytes  (maximum byte value)
Overflow amount:         239 bytes  (255 - 16 = 239 bytes past the buffer)

239 bytes of heap overflow. That's enough to corrupt multiple adjacent heap allocations, overwrite heap allocator metadata, or stomp on other critical data structures.

The Attack Scenario

An attacker on the same network segment (or any network that can reach the DHCP server) can craft a malicious DHCP DISCOVER or REQUEST packet with hlen set to 0xFF (255). No authentication is required — DHCP is a broadcast protocol by design. When the server processes this packet and calls dhcp_client_alloc, the oversized SMEMCPY corrupts up to 239 bytes of heap memory beyond the chaddr buffer.

Attacker sends crafted DHCP packet:
┌─────────────────────────────────────────┐
  DHCP DISCOVER                          
  hlen = 0xFF  (255  attacker-set)      
  chaddr = [255 bytes of attacker data]  
└─────────────────────────────────────────┘
           
           
Server calls: SMEMCPY(node->chaddr, msg->chaddr, 255)
                                              ^^^
                               Only 16 bytes allocated!
                               239 bytes overflow into heap

Depending on the heap layout and platform, this could result in:
- Denial of Service — heap corruption crashes the DHCP server or the entire system
- Remote Code Execution — carefully crafted overflow data overwrites a function pointer or return address
- Privilege Escalation — if the DHCP server runs with elevated privileges (common in embedded systems)

The Secondary Issue: Unbounded sprintf

The same file also contained two calls to sprintf when constructing IP address strings:

// VULNERABLE CODE (before fix)
sprintf(p, "%d", DHCPD_CLIENT_IP_MIN);
// ...
sprintf(p, "%d", DHCPD_CLIENT_IP_MAX);

sprintf writes into a buffer without any length limit. If the format string produces more characters than the remaining buffer space, this is another classic buffer overflow. While less immediately exploitable than the chaddr issue, it's still a latent bug waiting for the right conditions.


The Fix

Bounding the Memory Copy

The fix for the primary vulnerability is elegant in its simplicity — a ternary bounds check inline with the copy:

// FIXED CODE (after fix)
SMEMCPY(node->chaddr, msg->chaddr, 
        (msg->hlen > sizeof(node->chaddr)) ? sizeof(node->chaddr) : msg->hlen);

This is a classic min() pattern: copy whichever is smaller — the attacker-supplied length or the actual size of the destination buffer. Now, no matter what value an attacker puts in hlen, the copy will never exceed the bounds of node->chaddr.

Let's visualize the protection:

Before fix:
  hlen = 255  →  copies 255 bytes  →  OVERFLOW (239 bytes past buffer)

After fix:
  hlen = 255  →  min(255, 16) = 16  →  copies 16 bytes  →  SAFE
  hlen = 6    →  min(6, 16)  = 6   →  copies 6 bytes   →  SAFE (normal Ethernet)
  hlen = 0    →  min(0, 16)  = 0   →  copies 0 bytes   →  SAFE

Many codebases define a MIN() macro for readability. The inline ternary achieves the same result and makes the intent explicit at the call site.

Replacing sprintf with snprintf

The secondary fix replaces the unbounded sprintf calls with snprintf, which accepts a maximum length argument:

// FIXED CODE (after fix)
snprintf(p, (size_t)(str_tmp + sizeof(str_tmp) - p), "%d", DHCPD_CLIENT_IP_MIN);
// ...
snprintf(p, (size_t)(str_tmp + sizeof(str_tmp) - p), "%d", DHCPD_CLIENT_IP_MAX);

The size argument (size_t)(str_tmp + sizeof(str_tmp) - p) calculates exactly how many bytes remain in the buffer from the current write position p to the end of str_tmp. This is a precise and correct way to prevent overflows when writing into a substring of a larger buffer.

The Full Diff at a Glance

- SMEMCPY(node->chaddr, msg->chaddr, msg->hlen);
+ SMEMCPY(node->chaddr, msg->chaddr, (msg->hlen > sizeof(node->chaddr)) ? sizeof(node->chaddr) : msg->hlen);

- sprintf(p, "%d", DHCPD_CLIENT_IP_MIN);
+ snprintf(p, (size_t)(str_tmp + sizeof(str_tmp) - p), "%d", DHCPD_CLIENT_IP_MIN);

- sprintf(p, "%d", DHCPD_CLIENT_IP_MAX);
+ snprintf(p, (size_t)(str_tmp + sizeof(str_tmp) - p), "%d", DHCPD_CLIENT_IP_MAX);

Three lines changed. A critical vulnerability closed.


Prevention & Best Practices

1. Never Trust Length Fields from the Network

This is the golden rule of network programming: any field in a network packet is attacker-controlled. Length fields, offsets, counts — all of them must be validated against your own known-good values before use.

// ALWAYS validate before using network-supplied lengths
if (msg->hlen > sizeof(node->chaddr)) {
    // Log the anomaly, drop the packet, or clamp the value
    return NULL; // or: msg->hlen = sizeof(node->chaddr);
}
SMEMCPY(node->chaddr, msg->chaddr, msg->hlen);

2. Use Safe String and Memory Functions

Unsafe Function Safe Alternative Why
strcpy(dst, src) strncpy(dst, src, n) or strlcpy Bounded copy
sprintf(buf, fmt, ...) snprintf(buf, n, fmt, ...) Length-limited
gets(buf) fgets(buf, n, stdin) Bounded read
memcpy(dst, src, n) Validate n first Explicit bounds check
strcat(dst, src) strncat(dst, src, n) Bounded concatenation

3. Use sizeof for Buffer Size Calculations

Always use sizeof(buffer) rather than hardcoded constants. If the buffer size changes in a refactor, sizeof automatically reflects the new size:

// Fragile — breaks if buffer size changes
SMEMCPY(node->chaddr, msg->chaddr, 16);

// Robust — always correct
SMEMCPY(node->chaddr, msg->chaddr, 
        MIN(msg->hlen, sizeof(node->chaddr)));

4. Enable Compiler and Runtime Protections

Modern compilers and toolchains offer several layers of protection:

  • -D_FORTIFY_SOURCE=2 — GCC/Clang: enables compile-time and runtime checks on buffer functions
  • -fstack-protector-strong — adds stack canaries to detect stack overflows
  • AddressSanitizer (-fsanitize=address) — detects heap overflows at runtime during testing
  • Valgrind — runtime memory error detection
  • Static analyzers — tools like Coverity, CodeQL, or Clang Static Analyzer can catch these patterns before code ships

5. Fuzz Your Protocol Parsers

DHCP, DNS, HTTP, and other protocol parsers are prime targets for fuzzing. Tools like AFL++ or libFuzzer can automatically generate malformed packets — including ones with maximum-value length fields — to discover exactly this class of vulnerability.

# Example: fuzzing a network parser with AFL++
afl-fuzz -i corpus/ -o findings/ -- ./dhcp_parser_harness @@

6. Relevant Security Standards

This vulnerability maps to well-known security weakness classifications:

  • CWE-122: Heap-based Buffer Overflow
  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
  • CWE-20: Improper Input Validation
  • OWASP: A03:2021 – Injection (which includes memory injection via buffer overflows)
  • CERT C: Rule ARR38-C — Guarantee that library functions do not form invalid pointers

Conclusion

This vulnerability is a perfect illustration of a timeless truth in systems programming: the distance between a safe program and a critically vulnerable one can be as small as a missing bounds check. One SMEMCPY call, one unvalidated length field, and an attacker gains the ability to corrupt 239 bytes of heap memory in a network-facing service.

The fix is equally instructive — not a complex architectural change, but a simple, targeted bounds check that costs nothing in performance and closes the vulnerability completely. Good security is often less about heroic engineering and more about disciplined, consistent application of basic principles.

Key takeaways for developers:

  1. Validate all length fields from network packets before using them in memory operations
  2. Prefer snprintf over sprintf — there is almost never a good reason to use sprintf
  3. Use sizeof(buffer) in copy operations to make size relationships explicit and refactor-safe
  4. Fuzz your protocol implementations — automated fuzzing finds exactly these edge cases
  5. Enable compiler sanitizers in your test and CI builds to catch overflows before they reach production

Security vulnerabilities in network-facing C code are not inevitable — they are preventable with consistent application of well-understood practices. Every bounds check you add is an attack vector you close.


This vulnerability was automatically detected and fixed by OrbisAI Security. Automated security scanning can identify critical issues like this one before they reach production.

Frequently Asked Questions

What is a heap buffer overflow in DHCP servers?

A heap buffer overflow in DHCP servers occurs when the server copies more data than allocated into a heap-allocated buffer, typically by trusting packet fields like hardware address length without validation. This can overwrite adjacent memory structures, leading to crashes or code execution.

How do you prevent heap buffer overflows in C DHCP implementations?

Always validate packet field values against known buffer sizes before using them in memory operations. Use bounds-checked functions, implement maximum length constants, and never trust network-supplied length fields directly in memcpy() or similar operations.

What CWE is heap buffer overflow?

Heap buffer overflow is classified as CWE-122 (Heap-based Buffer Overflow), which is a child of CWE-787 (Out-of-bounds Write). In network protocol implementations, it often combines with CWE-120 (Buffer Copy without Checking Size of Input).

Is using strncpy() instead of strcpy() enough to prevent DHCP heap overflows?

No. While strncpy() helps with string operations, DHCP servers often use memcpy() for binary data. The key is validating the length parameter itself before any copy operation, not just choosing a "safer" function. The length field from the packet must be checked against the destination buffer size.

Can static analysis detect heap buffer overflows in DHCP servers?

Yes, modern static analysis tools can detect missing bounds checks before memory copy operations, especially when the length parameter comes from external input like network packets. Tools look for patterns where packet fields are used directly in memcpy() without validation against buffer size constants.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11372

Related Articles

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

high

How insecure string copy functions happen in C and how to fix it

A high-severity buffer overflow vulnerability was discovered in `tools/claude-vscode-wrapper.c`, where an unbounded `strcpy()` call copied a file path into a fixed-size `MAX_PATH` buffer without any size validation. The fix replaces `strcpy()` with `snprintf()` and swaps `malloc()` for `calloc()`, ensuring both string operations and memory allocation are bounds-safe and zero-initialized.

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.

critical

How Stack Buffer Overflows Happen in C with sprintf() and How to Fix Them

A critical stack buffer overflow was discovered in `libuv/Learn-libuv/docs/code/tty-gravity/main.c` where `sprintf()` wrote ANSI escape sequences and user-controlled variables into a fixed 500-byte buffer without any bounds checking. An attacker controlling the `pos`, `width`, or `message` variables could overflow the stack, overwrite return addresses, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()` and adds explicit length validation to ensure wr

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.