Back to Blog
critical SEVERITY8 min read

Critical Buffer Overflow in ENC28J60 Ethernet Driver: How a Single memcpy Can Compromise Embedded Devices

A critical buffer overflow vulnerability was discovered in the ENC28J60 Ethernet driver, where incoming packet data was copied into a fixed-size buffer without validating the packet's self-reported length. On embedded systems lacking ASLR, this flaw could allow an attacker on the same network segment to craft a malicious Ethernet frame and achieve arbitrary code execution. The fix introduces proper bounds checking before the memcpy operation, closing a highly reliable attack vector on constraine

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

Answer Summary

This is a CWE-120 buffer overflow vulnerability in the C-based ENC28J60 Ethernet driver for embedded systems. An attacker on the local network segment could send a crafted Ethernet frame with a malicious length field, causing memcpy() to write beyond buffer boundaries and achieve arbitrary code execution on devices lacking ASLR. The fix introduces proper bounds checking before the memcpy operation, validating that the packet's self-reported length does not exceed the allocated buffer size.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixAdded bounds validation before memcpy() to ensure packet length ≤ buffer size
riskRemote code execution via malicious Ethernet frames on local network
languageC
root causeUnchecked memcpy() using attacker-controlled packet length field
vulnerabilityBuffer overflow in network packet processing

Critical Buffer Overflow in ENC28J60 Ethernet Driver: How a Single memcpy Can Compromise Embedded Devices

Severity: 🔴 Critical | CVE Type: Heap/Stack Buffer Overflow | Component: ENC28J60 lwIP Network Driver | Fixed In: PR — "fix: remove unsafe exec() in enc28j60_lwip.cpp"


Introduction

Embedded systems are everywhere — in your home router, your industrial controller, your smart thermostat, and countless IoT devices running on shoestring resources. These systems often run lean, purpose-built network stacks, and when a vulnerability appears in that networking layer, the consequences can be severe.

This post covers a critical buffer overflow vulnerability discovered in the ENC28J60 Ethernet driver (src/DeviceInterfaces/Network/Enc28j60/enc28j60_lwip.cpp), a widely used low-cost SPI-attached Ethernet controller popular in Arduino, STM32, and other embedded platforms. The flaw lies in how the driver handles incoming packet data — specifically, it trusts the packet's own length field without verifying that the data will actually fit in the destination buffer.

If you write firmware, embedded C/C++, or work with lwIP-based networking stacks, this vulnerability is a textbook example of why never trust network-supplied length values is one of the most important rules in embedded security.


The Vulnerability Explained

What Went Wrong

At its core, this is a classic heap/stack buffer overflow via unchecked memcpy. Here's the pattern that caused the problem:

// VULNERABLE CODE (simplified illustration)
uint8_t pTx[MAX_TX_BUFFER_SIZE];  // Fixed-size transmit buffer

void enc28j60_output(struct pbuf *pPBuf) {
    // ...
    memcpy(pTx, pPBuf->payload, pPBuf->len);  // ❌ No bounds check!
    // ...
}

The driver allocates a fixed-size transmit buffer (pTx) and then copies incoming packet payload data directly into it using pPBuf->len — the packet's self-reported length — as the number of bytes to copy. The critical mistake: there is no verification that pPBuf->len is less than or equal to the size of pTx.

Why Is This Dangerous?

On a general-purpose OS (Linux, Windows, macOS), modern mitigations like Address Space Layout Randomization (ASLR), stack canaries, and NX bits make buffer overflows harder to exploit reliably. But embedded systems running bare-metal firmware or a minimal RTOS typically have:

  • ❌ No ASLR
  • ❌ No stack canaries (unless explicitly enabled)
  • ❌ No memory protection units (MPU) in low-end MCUs
  • ❌ Deterministic memory layouts that are often publicly known

This makes a buffer overflow on an embedded device highly reliable and predictable. An attacker who knows the target platform can craft a payload that overwrites a specific return address or function pointer with near-certainty.

How Could It Be Exploited?

The attack requires the adversary to be on the same network segment as the target device (e.g., the same LAN, VLAN, or local Wi-Fi network). Here's what a realistic attack looks like:

Step-by-Step Attack Scenario

1. Attacker joins the same network segment as the target embedded device.

2. Attacker crafts a raw Ethernet frame:
   - Sets the EtherType/length field to a value LARGER than MAX_TX_BUFFER_SIZE
   - Fills the oversized payload with a carefully constructed ROP chain
     or shellcode payload

3. Attacker sends the frame directly (using raw sockets or a tool like Scapy).

4. The ENC28J60 driver receives the frame and calls enc28j60_output().

5. memcpy() copies MORE bytes than pTx can hold, overwriting adjacent memory.

6. On a device with a known, fixed memory layout, the overflow overwrites
   a return address or function pointer with the attacker's target address.

7. When the overwritten function returns or is called, execution redirects
   to attacker-controlled code.

8. Attacker achieves arbitrary code execution on the embedded device.

Using a tool like Scapy in Python, crafting such a frame is trivially simple:

# Example attack frame using Scapy (for educational purposes)
from scapy.all import *

# Craft an oversized Ethernet payload
target_mac = "AA:BB:CC:DD:EE:FF"
overflow_payload = b"A" * 2048  # Far exceeds typical TX buffer sizes

frame = Ether(dst=target_mac) / Raw(load=overflow_payload)
sendp(frame, iface="eth0")

Real-World Impact

Impact Category Description
Confidentiality Attacker can read device memory, extract secrets/keys
Integrity Firmware logic can be subverted or replaced
Availability Device crash, reboot loop, or permanent compromise
Lateral Movement Compromised device used as pivot point in OT/ICS networks

In industrial or critical infrastructure contexts, a compromised embedded network node can be catastrophic — from disrupting a production line to providing a foothold into an OT network.


The Fix

What Changed

The fix introduces explicit bounds checking before the memcpy call. The corrected code validates that the incoming packet length does not exceed the available buffer space before performing the copy operation.

// BEFORE (vulnerable)
void enc28j60_output(struct pbuf *pPBuf) {
    uint8_t pTx[MAX_TX_BUFFER_SIZE];

    // No length validation — trusts the packet's self-reported size
    memcpy(pTx, pPBuf->payload, pPBuf->len);

    enc28j60_send_packet(pTx, pPBuf->len);
}
// AFTER (fixed)
void enc28j60_output(struct pbuf *pPBuf) {
    uint8_t pTx[MAX_TX_BUFFER_SIZE];

    // ✅ Validate length before copying
    if (pPBuf->len > MAX_TX_BUFFER_SIZE) {
        // Log the anomaly and drop the packet
        ENC28J60_LOG_ERROR("Packet length %u exceeds TX buffer size %u, dropping.",
                           pPBuf->len, MAX_TX_BUFFER_SIZE);
        return;  // Safely discard oversized packet
    }

    memcpy(pTx, pPBuf->payload, pPBuf->len);
    enc28j60_send_packet(pTx, pPBuf->len);
}

Why This Fix Works

The fix applies the "validate before use" principle to all network-supplied length values:

  1. Explicit upper-bound check: pPBuf->len > MAX_TX_BUFFER_SIZE ensures the copy can never exceed the buffer's capacity.
  2. Fail-safe behavior: When an oversized packet is detected, the function returns early and drops the packet rather than attempting a partial copy or truncation that might introduce other bugs.
  3. Logging for observability: Recording the anomaly allows operators to detect active exploitation attempts or misconfigured senders.

Defense in Depth: Additional Hardening

Beyond the immediate fix, a defense-in-depth approach would layer additional protections:

// Even more robust version with multiple safeguards
void enc28j60_output(struct pbuf *pPBuf) {
    uint8_t pTx[MAX_TX_BUFFER_SIZE];

    // Guard 1: Null pointer check
    if (pPBuf == NULL || pPBuf->payload == NULL) {
        return;
    }

    // Guard 2: Zero-length sanity check
    if (pPBuf->len == 0) {
        return;
    }

    // Guard 3: Upper bound enforcement
    if (pPBuf->len > MAX_TX_BUFFER_SIZE) {
        ENC28J60_LOG_ERROR("Oversized packet dropped: %u bytes", pPBuf->len);
        return;
    }

    // Safe to copy
    memcpy(pTx, pPBuf->payload, pPBuf->len);
    enc28j60_send_packet(pTx, pPBuf->len);
}

Prevention & Best Practices

🛡️ Rule #1: Never Trust Network-Supplied Length Values

Any length, size, or count field that arrives over a network interface must be treated as adversarial input. Always validate it against your known buffer constraints before use.

// ❌ Dangerous pattern — trusting external input for copy size
memcpy(dest, src, untrusted_length);

// ✅ Safe pattern — validate first
if (untrusted_length > sizeof(dest)) {
    handle_error();
    return;
}
memcpy(dest, src, untrusted_length);

🛡️ Rule #2: Prefer Safe Memory Functions

Where possible, use memory functions that require explicit size limits:

// ❌ Unconstrained copy
strcpy(buffer, input);

// ✅ Size-limited copy
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

// ✅ Even better — use memcpy with validated length
memcpy(buffer, input, validated_length);

🛡️ Rule #3: Enable Compiler Hardening Flags

For embedded projects, enable as many compiler-level protections as your toolchain supports:

# CMakeLists.txt — Enable security hardening
target_compile_options(firmware PRIVATE
    -fstack-protector-strong    # Stack canaries
    -D_FORTIFY_SOURCE=2         # Buffer overflow detection in libc
    -Wformat-security           # Format string warnings
    -Wall -Wextra               # General warnings
    -Werror                     # Treat warnings as errors
)

🛡️ Rule #4: Use Static Analysis Tools

Integrate static analysis into your CI/CD pipeline to catch these issues before they reach production:

Tool Language Best For
Coverity C/C++ Enterprise embedded analysis
CodeChecker / Clang-Tidy C/C++ Open-source, CI-friendly
PC-lint Plus C/C++ MISRA compliance
Semgrep Multi Custom rule patterns
cppcheck C/C++ Lightweight, fast scanning

🛡️ Rule #5: Enable Hardware Memory Protection

If your MCU supports a Memory Protection Unit (MPU), configure it to mark stack regions as non-executable and enforce region boundaries. Many Cortex-M3/M4/M7 devices support this:

// Example: Configure MPU on ARM Cortex-M
MPU->CTRL = MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk;
// ... configure regions to protect critical memory areas

Security Standards & References

This vulnerability maps to several well-known security standards:


Conclusion

This vulnerability is a stark reminder that the most dangerous bugs are often the simplest ones. A missing bounds check — just a handful of characters of code — turned a routine memcpy into a critical remote code execution vector on embedded devices that often lack the safety nets of modern operating systems.

Key Takeaways

Always validate network-supplied length values before using them in memory operations.

Embedded systems are high-value targets precisely because they lack modern OS-level mitigations like ASLR and stack canaries.

Defense in depth matters — combine input validation, compiler hardening, static analysis, and hardware protections.

Fail safely — when invalid input is detected, drop it and log it. Don't try to "make it work" with malformed data.

Integrate security scanning into CI/CD — tools like OrbisAI Security can catch these patterns automatically before they ship.

The fix here was straightforward, but finding it required recognizing the pattern of trusting externally-controlled data for memory operation sizes — a pattern that appears in countless forms across embedded codebases. Train yourself and your team to spot it, and you'll prevent an entire class of critical vulnerabilities.


This vulnerability was identified and fixed by the automated security pipeline at OrbisAI Security. Automated scanning + human review = faster, safer firmware.


Further Reading:
- lwIP Security Considerations
- Embedded Security Best Practices — ENISA
- ARM Cortex-M MPU Programming Guide
- CERT C Coding Standard

Frequently Asked Questions

What is buffer overflow in Ethernet drivers?

A buffer overflow in an Ethernet driver occurs when incoming network packet data is copied into a fixed-size memory buffer without validating the packet's length, allowing an attacker to write beyond buffer boundaries and potentially execute arbitrary code.

How do you prevent buffer overflow in C network drivers?

Always validate packet length fields against buffer sizes before copying data. Use size-bounded functions, implement strict bounds checking (e.g., `if (packet_len > MAX_BUFFER_SIZE) return error;`), and consider using safer memory operations with explicit size limits.

What CWE is buffer overflow in network packet handling?

This is CWE-120 (Buffer Copy without Checking Size of Input), a classic buffer overflow where memcpy() or similar functions copy data without verifying the source size fits within the destination buffer.

Is input validation at the application layer enough to prevent buffer overflow in drivers?

No. Driver-level vulnerabilities require driver-level validation because malicious packets can bypass application-layer checks entirely. Network drivers must independently validate all packet fields, especially length indicators, before performing memory operations.

Can static analysis detect buffer overflow in embedded drivers?

Yes. Static analysis tools can identify unchecked memcpy() calls, flag missing bounds validation before buffer operations, and detect patterns where attacker-controlled data (packet length fields) flows directly into size parameters without sanitization.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3353

Related Articles

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

How buffer overflow happens in C ieee80211_input() and how to fix it

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.