Back to Blog
critical SEVERITY7 min read

Heap Buffer Overflow in ShadowsocksR: How a Missing Bounds Check Could Let Attackers Crash Your Server

A critical heap buffer overflow vulnerability was discovered in ShadowsocksR-libev's server.c, where network-supplied data was copied into fixed-size heap buffers without verifying that the source length fit within the destination. An attacker could craft a malicious packet with an oversized length field to overflow the heap, potentially enabling remote code execution or denial of service. The fix adds proper bounds checking, null pointer validation after memory allocation, and reallocation fail

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

Answer Summary

This is a critical heap buffer overflow vulnerability (CWE-122) in ShadowsocksR-libev's C-language `server.c` file. The root cause is that network-supplied data was copied into fixed-size heap buffers without verifying the source length fit within the destination, allowing an attacker to send a crafted packet with an oversized length field to corrupt heap memory. The fix adds explicit bounds checking before any copy operation, null pointer validation after `malloc`/`realloc` calls, and safe handling of reallocation failures to prevent memory corruption.

Vulnerability at a Glance

cweCWE-122
fixAdd bounds check before copy, validate allocation results, handle realloc failure safely
riskRemote code execution or denial of service via crafted network packet
languageC
root causeNetwork-supplied length field used to copy data into heap buffer without bounds validation
vulnerabilityHeap Buffer Overflow

Heap Buffer Overflow in ShadowsocksR: How a Missing Bounds Check Could Let Attackers Crash Your Server

Introduction

Memory safety bugs remain some of the most dangerous and exploitable vulnerabilities in systems programming. Among them, heap buffer overflows occupy a particularly notorious position — they can be leveraged by remote attackers to crash services, corrupt memory, or in the worst case, achieve arbitrary code execution on a target system.

This post examines a critical heap buffer overflow discovered in shadowsocksr-libev, a widely used proxy tool for network traffic obfuscation. The vulnerability existed in src/server/server.c and could be triggered by any remote attacker capable of sending a crafted network packet — no authentication required.

Whether you're a C developer, a security engineer, or someone who runs ShadowsocksR in production, understanding this vulnerability and its fix will help you write safer, more resilient network code.


The Vulnerability Explained

What Is a Heap Buffer Overflow?

A heap buffer overflow occurs when a program writes more data into a heap-allocated memory region than that region can hold. Unlike stack overflows, heap overflows can be subtle and harder to detect — but they're equally dangerous. Depending on heap layout and allocator behavior, an overflow can corrupt adjacent heap metadata, overwrite function pointers, or enable sophisticated exploitation techniques like heap spraying.

Where Was the Bug?

The vulnerability had two distinct manifestations in server_recv_cb, the callback function that processes incoming network data.

Problem 1: Unchecked malloc Return Value

// VULNERABLE CODE (before fix)
char *back_buf = (char*)malloc(sizeof(buffer_t));
memcpy(back_buf, buf, sizeof(buffer_t));

When malloc fails (returns NULL), dereferencing the null pointer in the subsequent memcpy causes undefined behavior — typically a segmentation fault and crash. In a network server, this means a single malformed packet could bring down the entire process.

Problem 2: Unchecked brealloc Return Value Before memcpy

// VULNERABLE CODE (before fix)
size_t header_len = server->header_buf->len;
brealloc(server->header_buf, server->buf->len + header_len, BUF_SIZE);
memcpy(server->header_buf->array + header_len,
       server->buf->array, server->buf->len);
server->header_buf->len = server->buf->len + header_len;

Here's where things get serious. The brealloc function resizes a buffer, but its return value is never checked. If reallocation fails:

  • server->header_buf may point to freed or invalid memory
  • The subsequent memcpy writes attacker-controlled data (server->buf->len bytes) into that invalid region
  • Since server->buf->len is derived directly from the attacker's packet, an attacker can supply an arbitrarily large length value

The result: a classic attacker-controlled heap overflow.

How Could It Be Exploited?

An attacker targeting this vulnerability would:

  1. Connect to the ShadowsocksR server — no credentials needed, just a TCP connection
  2. Craft a malicious packet with an oversized len field in the buffer structure
  3. Send the packet during the handshake stage (STAGE_HANDSHAKE)
  4. Trigger the overflow when the server attempts to copy the oversized data into the under-allocated buffer

Potential impact:
- 🔴 Remote Denial of Service — crash the server process reliably
- 🔴 Heap Corruption — corrupt adjacent allocations, leading to unpredictable behavior
- 🔴 Remote Code Execution (RCE) — with sufficient heap manipulation, potentially hijack control flow

The CVSS score for this class of vulnerability in a network-facing daemon is typically 9.0+, firmly in critical territory.

The Bonus Bug: strncpy vs snprintf

The diff also reveals a subtler fix:

// BEFORE
strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1);

// AFTER
snprintf(svaddr.sun_path, sizeof(svaddr.sun_path), "%s", manager_address);

While strncpy with size - 1 is a common pattern, it has a well-known pitfall: it does not guarantee null-termination if the source string exactly fills the buffer. snprintf always null-terminates within the specified size, making it the safer choice for this kind of bounded string copy.


The Fix

The patch introduces three targeted changes that eliminate the vulnerability without altering program logic.

Fix 1: Check malloc Return Value

// FIXED CODE
char *back_buf = (char*)malloc(sizeof(buffer_t));
if (back_buf == NULL) {
    close_and_free_remote(EV_A_ remote);
    close_and_free_server(EV_A_ server);
    return;
}
memcpy(back_buf, buf, sizeof(buffer_t));

What changed: A null check is added immediately after malloc. If allocation fails, the connection is cleanly closed and the function returns — no crash, no undefined behavior.

Why it works: memcpy is never reached with a null destination pointer. The server gracefully handles memory pressure instead of crashing.

Fix 2: Check brealloc Return Value

// FIXED CODE
if (brealloc(server->header_buf, server->buf->len + header_len, BUF_SIZE) != 0) {
    close_and_free_remote(EV_A_ remote);
    close_and_free_server(EV_A_ server);
    return;
}
memcpy(server->header_buf->array + header_len,
       server->buf->array, server->buf->len);
server->header_buf->len = server->buf->len + header_len;

What changed: The return value of brealloc is now checked. A non-zero return indicates failure, and the code bails out cleanly before the memcpy.

Why it works: The memcpy only executes when we have a valid, correctly-sized destination buffer. An attacker can no longer trigger an overflow by sending a packet with an oversized length field — if the reallocation would fail or produce an insufficient buffer, the operation is aborted.

Fix 3: Safer String Copy

// FIXED CODE
snprintf(svaddr.sun_path, sizeof(svaddr.sun_path), "%s", manager_address);

What changed: strncpy replaced with snprintf, which guarantees null-termination.


Before vs. After: Side-by-Side

Aspect Before After
malloc failure Null pointer dereference Graceful connection close
brealloc failure Heap overflow via attacker-controlled len Graceful connection close
String copy Potentially non-null-terminated Always null-terminated
Attack surface Remote, unauthenticated Eliminated

Prevention & Best Practices

This vulnerability is a textbook example of C memory management pitfalls. Here's how to avoid similar issues in your own code:

1. Always Check Allocation Return Values

// ✅ CORRECT
void *ptr = malloc(size);
if (ptr == NULL) {
    // handle error
    return ERROR_OOM;
}

// ❌ WRONG
void *ptr = malloc(size);
memcpy(ptr, src, size); // UB if ptr is NULL

This applies to malloc, calloc, realloc, and any custom allocator wrappers.

2. Validate Lengths Before Copying

Never trust length values derived from network input:

// ✅ CORRECT
if (attacker_supplied_len > destination_buffer_size) {
    // reject or truncate
    return ERROR_INVALID_LENGTH;
}
memcpy(dest, src, attacker_supplied_len);

// ❌ WRONG
memcpy(dest, src, attacker_supplied_len); // overflow if len > dest size

3. Prefer Safer Alternatives

Unsafe Safer Alternative
strcpy snprintf, strlcpy
strncpy snprintf
sprintf snprintf
gets fgets
memcpy (unchecked) memcpy with bounds check

4. Use Modern Analysis Tools

Several tools can catch these bugs automatically:

  • AddressSanitizer (ASan) — detects heap overflows at runtime: gcc -fsanitize=address
  • Valgrind — memory error detection: valgrind --tool=memcheck ./server
  • Coverity / CodeQL — static analysis for null dereferences and buffer overflows
  • libFuzzer / AFL++ — fuzz network inputs to discover overflow conditions
  • clang-tidy — static linting with security-focused checks

5. Apply Defense in Depth

  • ASLR + PIE: Randomize memory layout to make exploitation harder
  • Stack canaries: Detect stack corruption (-fstack-protector-strong)
  • RELRO: Protect GOT/PLT from overwrites (-Wl,-z,relro,-z,now)
  • Seccomp: Restrict syscalls available to the server process

6. Relevant Security Standards

  • CWE-122: Heap-based Buffer Overflow
  • CWE-476: NULL Pointer Dereference
  • CWE-131: Incorrect Calculation of Buffer Size
  • OWASP: Memory Management Cheat Sheet
  • SEI CERT C: Rule MEM35-C (allocate sufficient memory), Rule MEM32-C (detect allocation errors)

Conclusion

This vulnerability in ShadowsocksR-libev is a reminder that network-facing C code demands extreme care around memory operations. Two missing null checks and one unchecked reallocation were all it took to expose a critical attack surface to any remote client.

The fix is elegant in its simplicity: check return values, handle failures gracefully, and never perform a memory copy without verifying the destination is valid and large enough. These are fundamentals of safe C programming — but under deadline pressure or in complex codebases, they're easy to miss.

Key takeaways:

  • ✅ Always check the return value of memory allocation functions
  • ✅ Never use attacker-controlled length values in memcpy without validation
  • ✅ Prefer snprintf over strncpy for bounded string copies
  • ✅ Use ASan and fuzzing in your CI pipeline for network-facing code
  • ✅ Treat all network input as potentially malicious — because it is

Security is not a feature you add at the end. It's a discipline you apply at every line.


This vulnerability was identified and fixed by automated security scanning. If you maintain C network code, consider integrating similar tooling into your development workflow.

Frequently Asked Questions

What is a heap buffer overflow?

A heap buffer overflow occurs when a program writes more data into a heap-allocated buffer than it was sized to hold, corrupting adjacent memory. In C, this typically happens when a length value from an untrusted source is used in a `memcpy` or similar operation without first verifying it does not exceed the buffer's allocated size.

How do you prevent heap buffer overflow in C?

Always validate that the number of bytes to be copied does not exceed the destination buffer's allocated size before performing any copy. Use safe allocation patterns that check for NULL returns, prefer bounded copy functions, and consider AddressSanitizer or Valgrind during development to catch overflows early.

What CWE is heap buffer overflow?

Heap buffer overflow is classified as CWE-122 (Heap-based Buffer Overflow), a subset of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is input validation alone enough to prevent heap buffer overflow in C?

Input validation is necessary but not sufficient on its own. You must also validate lengths at every copy site, check allocation return values for NULL, and handle reallocation failures without freeing or overwriting the original pointer — all of which were part of this fix.

Can static analysis detect heap buffer overflow?

Yes. Tools like Semgrep, Coverity, CodeQL, and AddressSanitizer (at runtime) can detect patterns where untrusted length values flow into buffer copy operations without intervening bounds checks. Orbis AppSec detected this exact pattern automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #658

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.