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

medium

How OAuth token audience bypass happens in Node.js serverless functions and how to fix it

A critical OAuth authentication vulnerability in a Netlify serverless function allowed any valid Google OAuth token—even those issued to completely different applications—to authenticate successfully. The fix adds proper audience (aud) claim verification using Google's tokeninfo endpoint to ensure only tokens issued specifically for this application are accepted.

high

How signature verification bypass happens in Node.js crypto and how to fix it

A high-severity signature verification bypass was discovered in `apps/panel/panel.js` where the `JMkey` variable was passed directly to `Buffer.from(JMkey, 'hex')` without validating its format. An attacker could supply a malformed hex string to cause silent failures or unexpected behavior in the RSA-SHA256 verification process, potentially bypassing signature checks entirely. The fix adds strict hex format validation before processing.

critical

How insecure nonce generation with Math.random() happens in Node.js HTTP Digest authentication and how to fix it

A critical vulnerability was discovered in `lib/cam.js` where the HTTP Digest authentication client nonce (cnonce) was generated using `Math.random().toString(36)` — a cryptographically insecure source of randomness. An attacker observing authentication exchanges could predict future cnonce values and forge valid authentication responses. The fix replaces this with `crypto.randomBytes(4).toString('hex')`, providing cryptographically secure random values.

critical

How missing authorization enforcement happens in Node.js Express routers and how to fix it

A critical authorization bypass was discovered in lib/router.js where readOnly and noDelete configuration options were only enforced through UI controls, not server-side middleware. Any authenticated user could bypass these restrictions by sending direct HTTP requests to perform destructive operations like database deletion or document modification. The fix adds Express middleware that enforces these security modes at the server level, blocking POST, PUT, and DELETE requests when appropriate.

critical

How command injection happens in Java Runtime.exec() and how to fix it

A critical command injection vulnerability was discovered in `ProcessUtils.java` where process IDs were concatenated directly into shell command strings passed to `Runtime.getRuntime().exec(String)`. This allowed shell metacharacter injection that could execute arbitrary commands. The fix switches to the array-based `exec(String[])` overload, which bypasses shell interpretation entirely.

critical

How Algolia API key exposure happens in EJS templates and how to fix it

A critical vulnerability in `layout/_plugins/global/config.ejs` was exposing Algolia API credentials — including `appId` and `apiKey` — directly in rendered HTML, making them visible to any user who inspects the page source. The fix removes raw credential interpolation from the template and replaces unsafe string embedding with properly sanitized output using `JSON.stringify()`. This eliminates the risk of unauthorized Algolia API access by anyone who visits the page.