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 command injection happens in Python subprocess and how to fix it

A critical command injection vulnerability was discovered in a CGI script that processed HTTP requests using `subprocess.check_output()` with `shell=True`. Attackers could inject arbitrary shell commands through URL parameters using metacharacters like semicolons, pipes, or backticks. The fix converts the command from a string to a list and sets `shell=False`, preventing shell interpretation of user input.

critical

How buffer overflow in URL parsing happens in C++ HTTP client and how to fix it

A critical buffer overflow vulnerability in the HTTP client's URL parsing function allowed attackers to overflow a stack-allocated host buffer through specially crafted URLs with excessively long hostnames. The vulnerability enabled arbitrary code execution by overwriting the return address. The fix adds proper bounds validation before the memcpy() operation to ensure the hostname length never exceeds the destination buffer size.

critical

How integer overflow in _wopendir() happens in C Windows dirent and how to fix it

A critical integer overflow vulnerability in `include/compat/dirent_msvc.h` allowed an attacker-controlled directory path length to wrap the `sizeof(wchar_t) * n + 16` allocation calculation, resulting in a dangerously undersized heap buffer. Subsequent writes to that buffer caused a heap overflow, enabling potential memory corruption or code execution on Windows systems. The fix adds a pre-allocation bounds check and proper errno signaling to safely reject overflow-inducing inputs.

critical

How buffer overflow happens in C xxd utility and how to fix it

A critical buffer overflow vulnerability was discovered in the xxd utility's `xxdline()` function where `strcpy()` was used without bounds checking on file input. An attacker could craft a malicious hex dump file with oversized lines to trigger memory corruption. The fix replaces the unsafe `strcpy()` with `snprintf()` to enforce buffer size limits.

critical

How buffer overflow in memcpy() happens in C/C++ embedded firmware and how to fix it

A critical buffer overflow vulnerability was discovered in the ESP32-based micro-journal firmware where `memcpy()` calls used `strlen()` without bounds checking, allowing oversized USB descriptor strings to corrupt adjacent memory. The fix replaces unbounded `strlen()` with `strnlen()` calls that enforce the destination buffer sizes (8, 16, and 4 bytes respectively), preventing heap/stack corruption from malicious USB devices.

high

How Denial of Service via crafted URI templates happens in Ruby addressable and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-35611) was discovered in the Ruby `addressable` gem versions prior to 2.9.0, which could allow attackers to crash or hang applications by sending specially crafted URI templates. The fix upgrades the dependency from version 2.8.7 to 2.9.0 across the Gemfile, Gemfile.lock, and gemspec in a Fastlane project, eliminating the vulnerable code path entirely.