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

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

high

How command injection happens in Node.js child_process and how to fix it

A high-severity command injection vulnerability was discovered in `bootstrap.js` at line 34, where the `exec()` function was used to run shell commands constructed from a `folder` variable. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates the shell interpolation entirely, closing the door on command injection attacks that could have affected all downstream consumers of this Node.js library.

high

How unsafe pickle deserialization happens in NumPy's np.load() and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `tools/ardy-engine/retarget.py` where `np.load()` was called with `allow_pickle=True`, enabling attackers to embed malicious pickle payloads in `.npz` files. The fix was a single-character change—switching `allow_pickle=True` to `allow_pickle=False`—that eliminates the deserialization attack vector while preserving the file's legitimate array data loading functionality.

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

How pickle-based arbitrary code execution happens in PyTorch and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `scripts/export_joyvasa_audio.py` where `torch.load()` was called with `weights_only=False`, allowing any pickle-serialized Python object — including malicious code — to execute during checkpoint loading. The fix switches to `weights_only=True` and explicitly allowlists only the two non-standard classes the checkpoint actually requires: `argparse.Namespace` and `pathlib.PosixPath`. This closes a real code execution path tha

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.