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++ and how to fix it

A critical buffer overflow in `create_hex_string()` within `hmlangw.cpp` let an unconditional 16-iteration loop write past the bounds of a 100-byte `hex` buffer using unchecked `sprintf` calls. The fix replaces `sprintf` with `snprintf` and caps the loop iterations based on the actual destination buffer size, closing off a memory corruption path reachable from serial or network input.

high

How insecure-use-string-copy-fn happens in C and how to fix it

A high-severity vulnerability was identified in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to handle path strings without proper bounds checking or guaranteed null-termination. The fix replaces `strcpy()` with direct character assignment and `strncpy()` with `snprintf()`, eliminating both buffer overflow and missing null-terminator risks in the plugin installation workflow.

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

high

How insecure string copy functions happen in C and how to fix it

A high-severity buffer overflow vulnerability was discovered in `tools/claude-vscode-wrapper.c`, where an unbounded `strcpy()` call copied a file path into a fixed-size `MAX_PATH` buffer without any size validation. The fix replaces `strcpy()` with `snprintf()` and swaps `malloc()` for `calloc()`, ensuring both string operations and memory allocation are bounds-safe and zero-initialized.

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.