Back to Blog
critical SEVERITY5 min read

How buffer overflow via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

O
By Orbis AppSec
Published July 6, 2026Reviewed July 6, 2026

Answer Summary

This is a CWE-120 buffer overflow vulnerability in C's zlib library (gzlib.c) caused by using strcpy() and strcat() without bounds checking on path and error message strings. The fix replaces unbounded strcpy()/strcat() calls at lines 224 and 586-588 with memcpy() operations that use pre-calculated string lengths, ensuring buffer writes never exceed allocated sizes.

Vulnerability at a Glance

cweCWE-120
fixReplace with memcpy() using pre-calculated lengths to enforce bounds
riskArbitrary code execution via heap buffer overflow
languageC
root causestrcpy() and strcat() used without verifying destination buffer capacity
vulnerabilityBuffer overflow via unbounded strcpy()/strcat()

How buffer overflow via strcpy() happens in C zlib and how to fix it

Introduction

The general/libzlib/gzlib.c file handles gzip file operations—opening compressed files and reporting errors. But a flaw in the fallback code path (used when NO_snprintf or NO_vsnprintf is defined) created a critical security risk. At line 224, strcpy(state->path, path) copies a file path into a heap-allocated buffer without any bounds verification. Similarly, at lines 586–588, three consecutive strcpy() and strcat() calls construct an error message by concatenating state->path, ": ", and msg into state->msg—again with no overflow protection.

This matters because zlib is one of the most widely deployed compression libraries in existence. Any application linking against this code that allows user-influenced file paths or error strings is potentially vulnerable to heap corruption and arbitrary code execution.

The Vulnerability Explained

The vulnerable code exists in two functions within gzlib.c:

1. Path copy in gz_open() (line 224):

#else
    strcpy(state->path, path);
#endif

Here, state->path is allocated with len + 1 bytes (where len is calculated from the input path). While the allocation should match the path length, strcpy() provides no guarantee that it won't write beyond the buffer. If there's any discrepancy between the calculated len and the actual null-terminated string length of path—for instance, due to concurrent modification or type casting issues with the const void *path parameter—a buffer overflow occurs.

2. Error message construction in gz_error() (lines 586–588):

#else
    strcpy(state->msg, state->path);
    strcat(state->msg, ": ");
    strcat(state->msg, msg);
#endif

This is the more dangerous pattern. Three separate unbounded string operations build an error message by:
1. Copying state->path into state->msg
2. Appending the literal ": "
3. Appending the error message msg

Each strcat() call scans for the null terminator and appends data, with zero verification that the combined result fits in state->msg. An attacker who can influence either the file path or trigger specific error conditions with long messages can overflow this buffer.

Attack scenario: An attacker provides a crafted file path to a program using zlib (e.g., a web server processing user-uploaded gzip files, or a utility accepting file paths from command-line arguments). If the path is long enough and triggers an error condition in gz_error(), the concatenation of path + ": " + error message overflows state->msg, corrupting heap metadata. This enables heap exploitation techniques—potentially redirecting execution flow to attacker-controlled code.

The Fix

The fix replaces all unbounded strcpy()/strcat() calls with memcpy() operations that use pre-calculated lengths:

Before (line 224):

strcpy(state->path, path);

After:

memcpy(state->path, (const char *)path, len + 1);

The len variable is already computed earlier in the function, so memcpy() copies exactly len + 1 bytes (including the null terminator)—no more, no less. This eliminates any possibility of writing beyond the allocated buffer.

Before (lines 586–588):

strcpy(state->msg, state->path);
strcat(state->msg, ": ");
strcat(state->msg, msg);

After:

{
    size_t path_len = strlen(state->path);
    size_t msg_len = strlen(msg);
    memcpy(state->msg, state->path, path_len);
    memcpy(state->msg + path_len, ": ", 2);
    memcpy(state->msg + path_len + 2, msg, msg_len + 1);
}

This replacement:
1. Calculates exact lengths upfront with strlen()
2. Uses pointer arithmetic to position each memcpy() at the correct offset
3. Copies exactly the right number of bytes for each segment
4. Includes the null terminator only in the final memcpy() (msg_len + 1)

The key security improvement is that memcpy() never scans for terminators or appends blindly—it copies a precise number of bytes to a precise location. Combined with the buffer allocation (which uses strlen(state->path) + strlen(msg) + 3), this guarantees writes stay within bounds.

Prevention & Best Practices

  1. Ban strcpy() and strcat() in new code. Use memcpy() with explicit lengths, snprintf(), or platform-specific safe alternatives like strlcpy()/strlcat() (BSD) or strcpy_s()/strcat_s() (C11 Annex K).

  2. Compile with warnings enabled. GCC's -Wstringop-overflow and -Wstringop-truncation flags catch many unsafe string operations at compile time.

  3. Use AddressSanitizer (ASan) in CI. Compile with -fsanitize=address to detect heap/stack buffer overflows during testing. The regression test included in this PR would immediately trigger ASan on the vulnerable code.

  4. Audit fallback code paths. The vulnerable code only executed when NO_snprintf was defined—a less common configuration. Security bugs often hide in rarely-executed branches. Ensure all code paths receive equal scrutiny.

  5. Prefer bounded APIs even when lengths are "known." Even if you've calculated the correct length, using memcpy() with that length is more robust than strcpy() because it creates a single source of truth for the copy size.

Key Takeaways

  • strcpy() in gz_open() line 224 was unsafe despite the buffer being allocated to the correct size—using memcpy(state->path, (const char *)path, len + 1) makes the contract explicit and verifiable.
  • Three consecutive strcpy()/strcat() calls in gz_error() are a classic concatenation overflow pattern—replacing them with offset-based memcpy() calls eliminates scanning for null terminators entirely.
  • The #else branch (when NO_snprintf is defined) was the vulnerable path—the snprintf() branch was already safe, demonstrating how conditional compilation can create security asymmetries.
  • Pre-calculating string lengths once and reusing them is both more secure (bounded copies) and more performant (avoids repeated strlen() scans by strcat()).
  • zlib's ubiquity amplifies this vulnerability—any application linking this code on platforms without snprintf support inherits the buffer overflow risk.

How Orbis AppSec Detected This

  • Source: File path string passed to gz_open() via the path parameter, and error message string msg passed to gz_error()
  • Sink: strcpy(state->path, path) at gzlib.c:224 and strcpy(state->msg, state->path) / strcat(state->msg, msg) at gzlib.c:586-588
  • Missing control: No bounds checking or length validation before string copy operations in the NO_snprintf fallback code path
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Replaced strcpy()/strcat() with memcpy() calls using pre-calculated string lengths to enforce buffer boundaries

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

Buffer overflows from strcpy() and strcat() remain one of the most dangerous and exploitable vulnerability classes in C code. This specific instance in zlib's gzlib.c demonstrates how even well-maintained, widely-used libraries can harbor critical flaws in less-tested code paths. The fix is straightforward—replace unbounded string operations with memcpy() using explicit lengths—but the security impact is profound: it closes a heap corruption vector that could enable arbitrary code execution in any application linking this library.

When writing C code that handles strings, always prefer bounded operations. Calculate your lengths once, allocate accordingly, and copy with precision.

References

Frequently Asked Questions

What is a buffer overflow via strcpy()?

A buffer overflow occurs when strcpy() copies a source string into a destination buffer without checking whether the source exceeds the destination's allocated size, writing past the buffer boundary and corrupting adjacent memory.

How do you prevent buffer overflow in C?

Use bounded copy functions like memcpy() with explicit length parameters, snprintf() instead of sprintf(), or strlcpy() where available. Always calculate and validate string lengths before copying.

What CWE is buffer overflow?

CWE-120 (Buffer Copy without Checking Size of Input), which covers classic buffer overflow scenarios where data is copied to a buffer without verifying the input fits within the allocated space.

Is using snprintf() enough to prevent buffer overflow?

snprintf() prevents overflow by truncating output to the specified buffer size, but it may silently truncate data. It's safer than strcpy() but you should still verify that truncation doesn't cause logic errors.

Can static analysis detect buffer overflow from strcpy()?

Yes, static analysis tools can flag strcpy() and strcat() usage as potentially unsafe. Tools like Semgrep, Coverity, and GCC's -Wstringop-overflow can detect many instances of unbounded string copies.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #21

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

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.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.