Back to Blog
critical SEVERITY8 min read

Buffer Over-Read in sslsniff.c find_library_path(): When strlen() Goes Out of Bounds

A high-severity buffer over-read vulnerability was discovered in `bpf/sslsniff.c` at line 515, where `memmove()` was called with a size derived from an unbounded `strlen()` on a pointer computed by searching within a fixed-size path buffer. An attacker able to influence `/proc` filesystem entries or `ldconfig` output could have triggered out-of-bounds memory reads or writes. The fix replaces the unsafe `strlen()` call with a bounds-aware `strnlen()` that constrains the operation to the remaining

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

Answer Summary

This is a critical buffer over-read vulnerability (CWE-126) in C code within `bpf/sslsniff.c`, specifically in the `find_library_path()` function at line 515. The root cause is an unbounded `strlen()` call on a pointer computed by searching within a fixed-size path buffer, which is then passed directly to `memmove()`. Because `strlen()` has no awareness of buffer boundaries, it can read past the end of the allocated buffer if the string is not properly null-terminated within bounds. The fix replaces `strlen()` with `strnlen()`, constraining the length calculation to the number of remaining valid bytes in the buffer, preventing any out-of-bounds memory access.

Vulnerability at a Glance

cweCWE-126
fixReplace strlen() with strnlen(ptr, remaining_bytes) to constrain length to valid buffer region
riskOut-of-bounds memory read or write; potential information disclosure or memory corruption
languageC
root causestrlen() called on an interior pointer into a fixed-size buffer with no upper bound, result passed to memmove()
vulnerabilityBuffer Over-Read via unbounded strlen() in memmove() size argument

Buffer Over-Read in sslsniff.c find_library_path(): When strlen() Goes Out of Bounds

Introduction

The bpf/sslsniff.c file is responsible for intercepting SSL/TLS traffic at the eBPF layer — a security-sensitive component that runs with elevated privileges on Linux systems. Inside its find_library_path() function, a single unsafe string operation quietly created the conditions for a high-severity memory safety bug. At line 515, a memmove() call was sized using an unbounded strlen() on a pointer derived from searching within a fixed stack buffer. The result: if the right conditions were met, the program could read memory beyond the buffer boundary — and potentially write it somewhere else.

This is the kind of bug that doesn't announce itself. No crash in normal operation, no obvious test failure. But under adversarial input, it becomes a reliable memory safety violation in a privileged, production eBPF tool.


The Vulnerability Explained

What the Code Was Doing

Inside find_library_path(), the code processes output lines from ldconfig, which typically look like:

libssl.so.1.1 -> /usr/lib/x86_64-linux-gnu/libssl.so.1.1

The function searches for the > character using strrchr(), checks that the next character is a space, and then uses memmove() to shift the path portion (after >) to the beginning of the path buffer:

// VULNERABLE CODE (before fix)
char *start = strrchr(path, '>');
if (start && *(start + 1) == ' ') {
    memmove(path, start + 2, strlen(start + 2) + 1);
    // ...
}

The Specific Problem: strlen(start + 2)

Here's where things go wrong. The path variable is a fixed-size stack buffer (declared with a compile-time size). The pointer start is computed by strrchr() — it points somewhere inside path. Then start + 2 advances two bytes further.

The critical issue: strlen(start + 2) has no knowledge of where the buffer ends. It will scan forward byte-by-byte looking for a null terminator, regardless of whether it has already passed the end of the allocated buffer. If start + 2 points near the tail of the buffer and the buffer is not properly null-terminated at that position, strlen() will read past the buffer boundary into adjacent stack memory.

Even worse, the result of that strlen() is then passed directly to memmove() as the byte count:

memmove(path, start + 2, strlen(start + 2) + 1);
//                        ^^^^^^^^^^^^^^^^^^^^
//                        Could be larger than remaining buffer space

If strlen() returns a value larger than the space between start + 2 and the end of path, the memmove() will copy bytes from beyond the buffer into path — a classic out-of-bounds read feeding into a write.

Attack Scenario

The PR notes that "an attacker who can influence /proc filesystem entries" could exploit this. More concretely:

  1. find_library_path() parses output that may be influenced by the filesystem or environment (e.g., a crafted ldconfig cache or symlink output).
  2. An attacker crafts a library path entry where the > character appears very close to the end of the path buffer, with no null terminator within the buffer after start + 2.
  3. strlen(start + 2) walks off the end of the stack buffer, reading adjacent stack frames.
  4. memmove() copies that out-of-bounds data into path, potentially exposing stack contents or corrupting adjacent variables.

In the context of an eBPF-based SSL sniffer running as root or with CAP_BPF, memory corruption or information leakage from the kernel-adjacent stack is a serious concern.

CWE Reference

This vulnerability maps to:
- CWE-125: Out-of-bounds Read
- CWE-787: Out-of-bounds Write (via the subsequent memmove)
- CWE-120: Buffer Copy without Checking Size of Input


The Fix

Before and After

The fix is surgical: replace the unbounded strlen() with a bounds-aware strnlen() that is explicitly limited to the number of bytes remaining in the buffer between start + 2 and the end of path.

Before (vulnerable):

memmove(path, start + 2, strlen(start + 2) + 1);

After (fixed):

size_t avail = (size_t)(path + sizeof(path) - (start + 2));
memmove(path, start + 2, strnlen(start + 2, avail) + 1);

Why This Works

Let's break down each piece of the fix:

1. Computing avail:

size_t avail = (size_t)(path + sizeof(path) - (start + 2));

This calculates exactly how many bytes remain in the path buffer starting from start + 2. path + sizeof(path) is a pointer to one-past-the-end of the buffer — a well-defined sentinel in C. Subtracting start + 2 gives the maximum number of bytes we are allowed to examine.

2. Using strnlen() instead of strlen():

strnlen(start + 2, avail)

strnlen() will scan for a null terminator just like strlen(), but it stops after avail bytes even if no null terminator is found. This means it can never read past the end of the buffer. The returned length is guaranteed to be ≤ avail.

3. The memmove() is now safe:
Because strnlen() returns at most avail, and avail was computed as the remaining space in path, the memmove() cannot copy more bytes than the buffer can hold from the source position. The buffer boundary is respected.

The Regression Test

The PR also introduces tests/test_invariant_sslsniff.c, which validates four security invariants across 21 adversarial payloads:

  1. The function must never read beyond the input buffer boundary
  2. The function must never write beyond the output buffer boundary
  3. The resulting string must be null-terminated within bounds
  4. The resulting string length must be ≤ the original buffer size

The test uses canary values (0xAB and 0xCD byte patterns) placed adjacent to the buffer to detect any overflow, and validates boundary conditions at every offset position near the buffer edge. This is exactly the kind of property-based boundary testing that catches off-by-one errors that unit tests often miss.


Prevention & Best Practices

1. Treat Every Computed Pointer as Potentially Near the Buffer Edge

Whenever you compute a pointer by searching within a buffer (via strrchr, strstr, strchr, etc.) and then advance it by an offset, ask: what is the maximum distance from this pointer to the end of the buffer? Always compute avail before any subsequent string operation.

// Pattern to follow:
char *found = strrchr(buf, '>');
if (found) {
    char *src = found + 2;
    size_t avail = (size_t)(buf + sizeof(buf) - src);
    size_t len = strnlen(src, avail);
    // Now len is safe to use
}

2. Prefer strnlen Over strlen on Internal Buffer Pointers

strlen() is only safe when you have a guarantee that a null terminator exists within the buffer. For pointers computed at runtime within a buffer, that guarantee rarely holds without explicit enforcement. Use strnlen() with a computed maximum.

3. Use Compiler and Runtime Protections

Enable the following for C/C++ code in security-sensitive components:
- -fstack-protector-strong — detects stack buffer overflows at runtime
- -D_FORTIFY_SOURCE=2 — enables compile-time and runtime bounds checking for common string functions
- AddressSanitizer (-fsanitize=address) during testing — would have caught this exact bug
- Static analysis tools like cppcheck, clang-tidy, or Coverity with buffer overflow rules enabled

4. For eBPF and Privileged C Code, Apply Extra Scrutiny

Code that runs with elevated privileges or processes externally-influenced data (like filesystem paths, /proc entries, or network data) should be treated as a security boundary. Every string operation that touches external data deserves a manual review for bounds safety.

5. OWASP and Standards References


Key Takeaways

  • strlen() on a sub-pointer is dangerous: Using strlen(start + 2) where start is derived from strrchr() within a fixed buffer is a classic pattern for out-of-bounds reads. Always use strnlen() with an explicitly computed limit.

  • The avail calculation is the critical guard: size_t avail = (size_t)(path + sizeof(path) - (start + 2)) is the correct idiom for computing remaining buffer space from a derived pointer — memorize it.

  • memmove() is only as safe as its size argument: The function itself is bounds-safe, but it will faithfully copy whatever count you give it. A corrupted count from an unbounded strlen() turns a safe function into an unsafe one.

  • eBPF and BPF tools deserve memory-safety audits: Tools like sslsniff.c parse data from the OS environment with elevated privileges. A memory safety bug here has higher impact than in a typical userspace utility.

  • Regression tests with canary values catch what unit tests miss: The new test_invariant_sslsniff.c uses 0xAB/0xCD canaries and boundary-edge payloads — this approach should be standard for any C function that processes variable-length string data.


Conclusion

A single strlen() call in find_library_path() was the difference between safe and unsafe memory access in a privileged eBPF tool. The fix — replacing strlen(start + 2) with strnlen(start + 2, avail) after computing the available buffer space — is minimal in size but significant in impact. It closes an out-of-bounds read/write path that could have been triggered by crafted filesystem or ldconfig output.

The broader lesson is one of defensive discipline: in C, every pointer arithmetic operation that feeds into a string or memory function must be accompanied by an explicit bounds calculation. When working in security-sensitive, privileged code like an SSL traffic interceptor, that discipline isn't optional — it's the baseline. The new regression test suite ensures this specific class of boundary violation cannot quietly re-enter the codebase.

Frequently Asked Questions

What is a buffer over-read?

A buffer over-read occurs when a program reads data beyond the end of an allocated buffer, potentially exposing sensitive memory contents or causing a crash. In C, functions like strlen() that rely on null-terminator detection are common culprits when used on pointers that may not be null-terminated within buffer bounds.

How do you prevent buffer over-read in C?

Use bounds-aware alternatives to unsafe string functions: replace strlen() with strnlen(), strcpy() with strncpy() or strlcpy(), and strcat() with strncat() or strlcat(). Always track the remaining capacity of a buffer when working with interior pointers, and pass that remaining size as the bound to any length-computing or copying function.

What CWE is buffer over-read?

Buffer over-read is classified as CWE-126 (Buffer Over-Read). It is a subtype of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) and is closely related to CWE-125 (Out-of-bounds Read).

Is adding a null terminator enough to prevent buffer over-read?

Not always. Even if you null-terminate a buffer, an interior pointer computed by searching within that buffer (e.g., via strchr() or strstr()) may point to a position where the remaining bytes before the buffer's end are fewer than the full string length. strlen() on such a pointer can still read past the buffer's end if the data is malformed or attacker-controlled.

Can static analysis detect buffer over-read from strlen()?

Yes. Static analysis tools like Semgrep, Coverity, and clang-analyzer can flag patterns where strlen() is called on a pointer derived from an offset into a fixed-size buffer and the result is used as a size argument to memory manipulation functions like memmove() or memcpy(). Orbis AppSec detected exactly this pattern automatically in sslsniff.c.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

high

How heap buffer overflow happens in C JMA archive extraction and how to fix it

A heap buffer overflow vulnerability in `jma/jma.cpp` allowed a crafted JMA ROM archive to trigger out-of-bounds memory writes during file extraction. The flaw existed at line 446, where `memcpy` was called with `first_chunk_offset` and `copy_amount` values derived directly from archive header metadata without any validation that those values stayed within the bounds of either the source or destination buffer. The fix adds a pre-copy bounds check that rejects malformed archives before the danger

critical

How unsafe buffer copying happens in C credential storage and how to fix it

A critical vulnerability in `lib/server.c` allowed attackers to trigger out-of-bounds memory reads when copying credentials via unsafe `memcpy()` calls. By replacing `memcpy()` with bounds-safe `strlcpy()`, the fix ensures credentials are safely stored without buffer overruns or null-termination issues.

critical

How buffer overflow happens in C Bluetooth device handling and how to fix it

A critical buffer overflow vulnerability in `src/wiiuse.c` allowed attackers within Bluetooth range to trigger heap corruption by sending specially crafted HID packets with oversized length values. The fix adds strict bounds checking to validate that data lengths don't exceed buffer capacity before performing memory operations, preventing exploitation by malicious or intercepted Bluetooth devices.

critical

How buffer overflow happens in C patches.c sprintf macros and how to fix it

A critical buffer overflow vulnerability was discovered in `src/patches.c` where the `_EPRINT_I`, `_EPRINT_F`, and `_EPRINT_COEF` macros used `sprintf()` to write formatted AMY event data into a fixed-size buffer without any bounds checking. By replacing every `sprintf()` call with `snprintf()` and tracking remaining buffer space using a `s_entry` base pointer, the fix ensures that formatting 22 event fields — even at maximum values — can never write beyond the buffer boundary.

critical

How buffer overflow happens in C dcraw_lz.c nikon_3700() and how to fix it

A critical buffer overflow vulnerability was discovered in `lightcrafts/coprocesses/dcraw/dcraw_lz.c` at line 1334, where the `nikon_3700()` function used `strcpy()` to copy camera make and model strings into fixed 64-byte buffers without any bounds checking. A crafted RAW image file with oversized make/model metadata could trigger a heap or stack corruption, potentially enabling arbitrary code execution. The fix replaces both `strcpy()` calls with `strncpy()` and explicit null-termination, enfo

critical

How buffer overflow in modxo_queue.c memcpy happens in C embedded systems and how to fix it

A critical buffer overflow vulnerability was discovered in `modxo/modxo_queue.c`, where two `memcpy` operations in the `modxo_queue_insert` and `modxo_queue_remove` functions used `queue->item_size` as the copy length without validating it against the destination buffer's bounds. If `item_size` was corrupted or maliciously set to an oversized value, both the enqueue (line 49) and dequeue (line 61) operations could overflow adjacent heap or stack memory on the embedded target. The fix adds bounds