Back to Blog
critical SEVERITY6 min read

How out-of-bounds read via unchecked memcpy happens in C packet processing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `hep-tester/heptester.c` where `memcpy` calls at lines 200-201 read from fixed offsets in a packet buffer without verifying the buffer was large enough. An attacker could send a crafted packet shorter than 18 bytes to trigger the read, potentially leaking memory contents or crashing the capture agent. The fix adds a single bounds check against `pkthdr->caplen` before any memory copy operations.

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

Answer Summary

This is a critical out-of-bounds read vulnerability (CWE-125) in C packet processing code where `memcpy` reads from fixed offsets (12 and 16) in a network packet buffer without validating that the buffer contains at least 18 bytes. The fix is a bounds check (`if (pkthdr->caplen < 18) return;`) added before the `memcpy` calls in `heptester.c`, ensuring truncated or malformed packets are rejected before accessing memory beyond the buffer's actual length.

Vulnerability at a Glance

cweCWE-125
fixAdded `if (pkthdr->caplen < 18) return;` before the memcpy calls
riskMemory disclosure or crash from crafted network packets
languageC
root causeMissing buffer length validation before memcpy at fixed offsets
vulnerabilityOut-of-bounds read via unchecked memcpy

How Out-of-Bounds Read via Unchecked memcpy Happens in C Packet Processing and How to Fix It

Introduction

The hep-tester/heptester.c file handles network packet capture and processing for the HEP (Homer Encapsulation Protocol) testing tool. Inside the callback_proto function — a pcap callback that processes every captured packet — a critical flaw at line 200 allowed an attacker to trigger an out-of-bounds memory read simply by sending a packet shorter than 18 bytes.

The vulnerable code blindly called memcpy to extract Ethernet and MPLS addresses from fixed offsets (12 and 16) within the packet buffer, without ever checking whether the packet was actually long enough to contain data at those positions. For developers working with raw network data in C, this is a textbook example of why every buffer access needs explicit length validation.

The Vulnerability Explained

Here's the vulnerable code path in callback_proto:

void callback_proto(u_char *useless, struct pcap_pkthdr *pkthdr, u_char *packet)
{
  /* Pat Callahan's patch for MPLS */
  unsigned char ethaddr[3], mplsaddr[3];

  memcpy(&ethaddr, (packet + 12), 2);
  memcpy(&mplsaddr, (packet + 16), 2);

The pcap_pkthdr structure contains caplen — the actual number of bytes captured for this packet. However, the code never checks pkthdr->caplen before reading from packet + 12 and packet + 16.

Why this is dangerous:

When memcpy(&ethaddr, (packet + 12), 2) executes, it reads 2 bytes starting at offset 12 in the packet buffer. If the packet is only, say, 5 bytes long, this reads 2 bytes from memory that doesn't belong to this packet. Similarly, memcpy(&mplsaddr, (packet + 16), 2) requires the buffer to be at least 18 bytes (offset 16 + 2 bytes).

Concrete attack scenario:

An attacker sends a crafted or truncated network packet shorter than 18 bytes to the network interface being monitored by the capture agent. When pcap delivers this packet to callback_proto:

  1. pkthdr->caplen might be 4, 10, or any value less than 18
  2. The packet pointer points to a buffer of only caplen bytes
  3. memcpy reads past the end of this buffer
  4. This results in either:
    - Information disclosure: Reading adjacent memory that may contain sensitive data from other packets or internal state
    - Crash: If the read crosses a page boundary into unmapped memory, triggering a segmentation fault and denial of service

The PR description also notes that similar patterns exist at lines 201, 202, 478, and 486, suggesting this class of bug may be systemic throughout the packet processing logic.

The Fix

The fix is elegant in its simplicity — a single bounds check added at line 200, immediately before the first memcpy:

Before:

  /* Pat Callahan's patch for MPLS */
  unsigned char ethaddr[3], mplsaddr[3];

  memcpy(&ethaddr, (packet + 12), 2);
  memcpy(&mplsaddr, (packet + 16), 2);

After:

  /* Pat Callahan's patch for MPLS */
  unsigned char ethaddr[3], mplsaddr[3];

  if (pkthdr->caplen < 18) return;
  memcpy(&ethaddr, (packet + 12), 2);
  memcpy(&mplsaddr, (packet + 16), 2);

Why 18 bytes? The highest offset accessed is 16, and 2 bytes are read from that position: 16 + 2 = 18. Any packet with caplen < 18 cannot safely provide the data these memcpy calls need.

Why return instead of error handling? In a pcap callback, returning early simply skips processing this packet. A truncated packet that doesn't contain valid Ethernet/MPLS headers isn't useful for the tool's purposes anyway — silently dropping it is the correct behavior.

The fix also includes a regression test (tests/test_invariant_heptester.c) that codifies the security invariant:

static int safe_extract_addresses(const uint8_t *packet, size_t packet_len,
                                   uint16_t *ethaddr, uint16_t *mplsaddr)
{
    /* Security invariant: must have at least 18 bytes to read offsets 12-13 and 16-17 */
    if (packet_len < 18) {
        return -1; /* Reject truncated packets */
    }
    memcpy(ethaddr, packet + 12, 2);
    memcpy(mplsaddr, packet + 16, 2);
    return 0;
}

The test exercises boundary conditions: empty buffers (0 bytes), one-byte-short buffers (17 bytes), minimum valid (18 bytes), and normal packets (100 bytes).

Prevention & Best Practices

1. Validate before every fixed-offset access:
Any time you read from a buffer at a computed or fixed offset, verify the buffer length first. This is especially critical in network code where packet lengths are attacker-controlled.

/* Pattern to follow: */
if (buffer_len < required_minimum) {
    return ERROR_CODE;
}
/* Only now is it safe to access buffer[offset] */

2. Use defensive macros for packet parsing:
Consider creating a bounds-checked read macro:

#define SAFE_READ(dst, src, offset, len, buflen) \
    do { if ((offset) + (len) > (buflen)) return -1; \
         memcpy((dst), (src) + (offset), (len)); } while(0)

3. Enable AddressSanitizer during development:
Compile with -fsanitize=address during testing. ASan would immediately flag this out-of-bounds read when processing a short test packet.

4. Audit all similar patterns:
As noted in the PR, lines 201, 202, 478, and 486 contain similar patterns. After fixing one instance, systematically review all memcpy calls that use fixed offsets from packet buffers.

5. Fuzz test packet processing:
Use AFL or libFuzzer to generate packets of various lengths, including very short ones. This would catch this class of bug automatically.

Key Takeaways

  • Every memcpy from a network packet buffer at a fixed offset needs a preceding length check against pkthdr->caplen — this is non-negotiable in pcap callback functions.
  • The minimum buffer size is the highest offset plus the number of bytes read: offset 16 + 2 bytes = 18 bytes minimum for callback_proto.
  • Truncated packets are not exceptional — they occur naturally (capture snaplen limits, fragmentation) and maliciously (crafted attacks). Code must handle them gracefully.
  • A single missing bounds check in heptester.c:200 exposed the entire capture agent to remote crash or memory disclosure from any host that could send packets to the monitored interface.
  • Regression tests should encode the security invariant (minimum packet length) independently of the implementation, so refactors don't silently reintroduce the bug.

How Orbis AppSec Detected This

  • Source: Network packet data received via pcap capture (u_char *packet parameter in callback_proto)
  • Sink: memcpy(&ethaddr, (packet + 12), 2) and memcpy(&mplsaddr, (packet + 16), 2) at hep-tester/heptester.c:200-201
  • Missing control: No validation that pkthdr->caplen >= 18 before accessing packet data at offsets 12 and 16
  • CWE: CWE-125 (Out-of-bounds Read)
  • Fix: Added if (pkthdr->caplen < 18) return; before the memcpy calls to reject packets too short to contain valid Ethernet/MPLS header data

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates a fundamental truth about C network programming: the data you receive from the network is adversarial. Every byte offset, every memcpy, every array index into a packet buffer is a potential out-of-bounds access if you haven't validated the buffer length first. The fix — a single if statement — is trivial, but the consequences of its absence are severe: remote crash or information leakage from any host that can put packets on the wire.

If you're writing pcap callbacks or any packet processing code in C, audit every fixed-offset access today. The pattern is simple: check length, then read. Never the other way around.

References

Frequently Asked Questions

What is an out-of-bounds read vulnerability?

An out-of-bounds read occurs when a program reads data from a memory location beyond the boundaries of the allocated buffer, potentially exposing sensitive data or causing crashes.

How do you prevent out-of-bounds reads in C?

Always validate buffer lengths before accessing data at fixed offsets, use bounds-checked alternatives to memcpy when possible, and treat all external input (including network packets) as potentially malformed.

What CWE is out-of-bounds read?

CWE-125: Out-of-bounds Read, which describes reading data past the end or before the beginning of the intended buffer.

Is using memcpy safely enough to prevent buffer over-reads?

No — memcpy itself doesn't perform bounds checking. You must manually verify that both the source and destination buffers are large enough for the copy operation before calling memcpy.

Can static analysis detect out-of-bounds reads?

Yes, static analysis tools can detect patterns where buffer accesses occur without prior length validation, especially when fixed offsets are used with variable-length input buffers like network packets.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #317

Related Articles

critical

How heap buffer overflow happens in C UART response handling and how to fix it

A critical heap buffer overflow vulnerability was discovered in the AT client response handler (`sm_at_client.c`) where incoming UART data was copied into a fixed-size buffer without verifying available capacity. A compromised modem or malicious UART data could trigger arbitrary heap corruption. The fix replaces an assertion-only guard with proper bounds clamping using `MIN()` to ensure writes never exceed the `at_cmd_resp` buffer allocation.

critical

How unsigned binary downloads happen in Dart update services and how to fix it

A critical vulnerability in the YourSSH application's update service allowed attackers to serve malicious binaries through man-in-the-middle attacks. The `downloadAsset()` function in `update_service.dart` downloaded application binaries directly from URLs without any cryptographic signature or integrity verification. The fix adds SHA-256 digest validation using the GitHub Releases API's digest field, ensuring only authentic binaries are installed.

high

NULL Pointer Dereference in ESP8266 user_interface.c wifi_station_set_default_hostname()

A critical NULL pointer dereference vulnerability in the ESP8266 firmware's `user_interface.c` allowed attackers to crash devices by exhausting the limited 80KB heap memory. The `wifi_station_set_default_hostname()` function's `os_malloc` call lacked a proper NULL guard, causing `ets_sprintf` to write to address 0 when allocation failed. The fix corrected a logic inversion in the NULL check condition.

high

Integer Overflow in PlayerAnimation.cpp memcpy Size Calculations

A critical integer overflow vulnerability was discovered in `animation/PlayerAnimation.cpp` where `vCount * sizeof(float) * 3` calculations could wrap around on 32-bit platforms when processing malicious animation files. An attacker could craft a model file with an oversized vertex count to trigger a heap buffer overflow via memcpy. The fix adds bounds checks against `SIZE_MAX` before all size computations used in memory copy operations.

critical

Critical OIDC Cache Key Collision in LiteLLM: Authentication Bypass & Privilege Escalation

LiteLLM versions prior to 1.87.0 contained a critical vulnerability in OIDC userinfo caching that allowed attackers to bypass authentication and escalate privileges through cache key collisions. By upgrading to version 1.87.0, applications eliminate the attack surface that could permit unauthorized users to assume the identity of legitimate authenticated users. This fix is essential for any production system using LiteLLM's OIDC integration.

critical

How buffer overflow in P-256 key decompression happens in C with mbedTLS and how to fix it

A critical buffer overflow vulnerability was discovered in `helpers/src/edhoc_cipher_suite_2.c` within the EDHOC cipher suite 2 implementation. The `mbedtls_ecp_decompress()` function used `raw_key_len` to copy a compressed peer public key into a fixed-size buffer without first verifying that the key length fit within the destination. An attacker sending a crafted EDHOC message with an oversized compressed key could exploit this to corrupt adjacent memory, potentially achieving remote code execu