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 Authentication Bypass happens in PyJWT and how to fix it

A critical authentication bypass vulnerability in PyJWT 2.12.1 allowed attackers to forge valid JSON Web Tokens, potentially bypassing application authentication mechanisms entirely. The vulnerability was fixed in PyJWT 2.13.0 through security improvements to token validation logic. This fix is essential for any application relying on JWT-based authentication.

high

How unsafe pickle deserialization happens in Keras/TensorFlow notebooks and how to fix it

A high-severity untrusted deserialization vulnerability was discovered in `TransferLearningTF.ipynb`, a transfer learning tutorial notebook that loads VGG16 model weights from the internet without verifying their integrity. Because Keras relies on Python's pickle-based serialization format under the hood, a tampered or substituted weights file could execute arbitrary code with the full privileges of the notebook user. The fix adds a SHA-256 checksum verification step immediately after the weight

critical

How Chromium launch-argument injection happens in Python Crawl4AI and how to fix it

A critical unauthenticated remote code execution vulnerability in Crawl4AI 0.8.9 allowed attackers to inject arbitrary Chromium launch arguments through the `browser_config.extra_args` parameter, potentially taking full control of the host process. The fix upgrades to Crawl4AI 0.9.0 and refactors the crawler initialization in `agent/tools/crawler.py` to use the new `BrowserConfig` and `CrawlerRunConfig` APIs, which enforce proper argument validation. This change eliminates the injection surface

critical

How integer overflow in buffer size calculation happens in C++ and how to fix it

A critical integer overflow vulnerability was discovered in OpenCV's HAL filter implementation where multiplying image dimensions without overflow protection could allocate dangerously undersized buffers. An attacker supplying crafted image dimensions (e.g., 65536×65536) could trigger heap corruption through out-of-bounds writes. The fix promotes the calculation to 64-bit arithmetic with a single cast.

critical

How hardcoded API key placeholders in documentation happen in Python and how to fix it

A high-severity security issue was discovered in the Context7 API documentation where a hardcoded API key placeholder (`CONTEXT7_API_KEY`) could be copied directly into production code. The fix replaced the static string with a proper environment variable reference using `os.environ`, preventing developers from accidentally deploying exposed credentials.

critical

How heap buffer overflow happens in C dupstr() and how to fix it

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v