Back to Blog
critical SEVERITY7 min read

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(

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

Answer Summary

This is a critical buffer overflow vulnerability (CWE-120) in C tar header parsing code where `strcpy()` and `sprintf()` functions lack bounds checking. When processing tar files with adversarial input, attackers could overflow buffers by providing non-null-terminated header fields. The fix replaces all unbounded string operations with bounded alternatives: `memcpy(dst, src, sizeof(dst))` followed by explicit null-termination, and `snprintf()` with size limits instead of unsafe `sprintf()`.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace strcpy() with memcpy() + null-termination and sprintf() with snprintf()
riskRemote code execution via malicious tar files; local privilege escalation
languageC
root causeUse of strcpy() and sprintf() without bounds checking on tar header fields
vulnerabilityBuffer overflow in tar header field copying

How Buffer Overflow Happens in C Tar Header Parsing and How to Fix It

Tar archives are ubiquitous in software distribution, but the C library that parses them can be a security minefield if not handled carefully. In this post, we'll examine a critical buffer overflow vulnerability discovered in microtar/microtar.c—a production tar parsing library—and walk through the exact fix that prevented potential code execution.


Introduction

The microtar/microtar.c file handles tar archive parsing, a core function in many build systems and package managers. However, the raw_to_header() and header_to_raw() functions at lines 114 and beyond contained a dangerous pattern: unbounded string copying using strcpy() and sprintf().

When processing tar files, these functions copy header fields (like filename and link name) from raw binary structures into destination buffers without checking if the source data fits. An attacker controlling a malicious tar file could craft header fields that are not null-terminated, causing strcpy() to read beyond the source buffer and overflow the destination—potentially corrupting the heap and leading to code execution.

This wasn't theoretical. The vulnerability existed in production code and was flagged as CRITICAL with an assessment of "Likely exploitable."


The Vulnerability Explained

The Dangerous Code Pattern

Let's look at the vulnerable code from microtar/microtar.c (lines 114-115):

static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
  // ... other code ...
  strcpy(h->name, rh->name);           // ← VULNERABLE
  strcpy(h->linkname, rh->linkname);   // ← VULNERABLE
  return MTAR_ESUCCESS;
}

And in the header_to_raw() function (lines 128-131):

sprintf(rh->mode, "%o", h->mode);      // ← VULNERABLE
sprintf(rh->owner, "%o", h->owner);    // ← VULNERABLE
sprintf(rh->size, "%o", h->size);      // ← VULNERABLE
sprintf(rh->mtime, "%o", h->mtime);    // ← VULNERABLE
strcpy(rh->name, h->name);             // ← VULNERABLE
strcpy(rh->linkname, h->linkname);     // ← VULNERABLE

Why This Is Dangerous

The tar header structures have fixed-size fields. For example, rh->name might be 100 bytes. When strcpy() copies from an untrusted source (a tar file), it reads until it finds a null terminator—but what if the source isn't null-terminated?

Attack scenario:
1. An attacker creates a malicious tar file with a name field that is exactly 100 bytes without a null terminator.
2. When raw_to_header() calls strcpy(h->name, rh->name), it copies all 100 bytes and keeps reading into adjacent memory looking for \0.
3. This read-past-bounds can overflow the destination buffer, corrupting the heap.
4. With careful crafting, this corruption could overwrite function pointers or other critical data, leading to code execution.

The Actual Threat

The tar parsing happens when:
- A user extracts an archive via CLI: myapp extract malicious.tar
- A build system automatically processes downloaded tar files
- A package manager unpacks dependencies

Since this is a local CLI tool, exploitation requires the attacker to control input files or command-line arguments. However, in supply chain scenarios (e.g., compromised package repositories), this is a realistic threat.


The Fix

The fix replaces all unbounded string operations with bounded alternatives. Here's the complete diff:

Change 1: Replace strcpy() with memcpy() + null-termination

Before:

strcpy(h->name, rh->name);
strcpy(h->linkname, rh->linkname);

After:

memcpy(h->name, rh->name, sizeof(h->name));
h->name[sizeof(h->name) - 1] = '\0';
memcpy(h->linkname, rh->linkname, sizeof(h->linkname));
h->linkname[sizeof(h->linkname) - 1] = '\0';

Why this works:
- memcpy(dst, src, sizeof(dst)) copies at most sizeof(dst) bytes, preventing overflow.
- Explicitly setting the last byte to '\0' ensures null-termination, even if the source wasn't null-terminated.
- This is safer than strncpy(), which doesn't guarantee null-termination.

Change 2: Replace sprintf() with snprintf()

Before:

sprintf(rh->mode, "%o", h->mode);
sprintf(rh->owner, "%o", h->owner);
sprintf(rh->size, "%o", h->size);
sprintf(rh->mtime, "%o", h->mtime);

After:

snprintf(rh->mode, sizeof(rh->mode), "%o", h->mode);
snprintf(rh->owner, sizeof(rh->owner), "%o", h->owner);
snprintf(rh->size, sizeof(rh->size), "%o", h->size);
snprintf(rh->mtime, sizeof(rh->mtime), "%o", h->mtime);

Why this works:
- snprintf() takes a size parameter and writes at most size - 1 bytes (reserving space for null terminator).
- This prevents format string overflows and buffer overflows when converting integers to octal strings.

Change 3: Safe filename copying in mtar_write_file_header()

Before:

strcpy(h.name, name);  // ← User-controlled input, unbounded

After:

snprintf(h.name, sizeof(h.name), "%s", name);

Why this works:
- snprintf() with "%s" format specifier safely copies the user-supplied filename with bounds checking.
- This is particularly important since name comes from user input.

Full Context of Changes

The PR changed two functions in microtar/microtar.c:

  1. raw_to_header() (lines 114-116): Converts raw tar header to structured format
  2. header_to_raw() (lines 128-139): Converts structured header back to raw format
  3. mtar_write_file_header() (line 343): Writes a new file header with user-supplied filename

All three now use bounded operations.


Verification & Testing

The fix was verified with a regression test that guards against future regressions:

TEST_P(SecurityTest, TarHeaderCopyDoesNotOverflow) {
    // Create raw header with adversarial data (no null terminator)
    mtar_raw_header_t raw_header;
    memset(&raw_header, 0, sizeof(raw_header));

    // Fill buffer completely without null terminator
    memcpy(raw_header.name, std::string(100, 'A').c_str(), 100);

    // Convert to header - this should NOT overflow
    mtar_header_t header;
    raw_to_header(&raw_header, &header);

    // Verify destination buffers are safely null-terminated
    EXPECT_EQ('\0', header.name[sizeof(header.name)-1]);
}

This test confirms that even when source data lacks null terminators, the destination buffers remain safely bounded.


Prevention & Best Practices

1. Never Use strcpy() or sprintf() in Production

These functions are fundamentally unsafe because they have no way to know the destination buffer size. Modern C coding standards recommend:
- Replace strcpy() with memcpy() + explicit null-termination or strdup()
- Replace sprintf() with snprintf()
- Replace strcat() with strncat() or snprintf()

2. Always Validate Input Length

When parsing binary formats (like tar headers), validate that fields are properly null-terminated before processing:

// Good: Explicit bounds check
if (strnlen(rh->name, sizeof(rh->name)) == sizeof(rh->name)) {
    // Field is not null-terminated—handle as error
    return MTAR_EFAIL;
}
strcpy(h->name, rh->name);  // Now safe

3. Use Static Analysis Tools

Enable compiler warnings and use static analysis:
- GCC/Clang: -Wall -Wextra -Wformat -Wformat-security
- Clang Static Analyzer: scan-build
- Semgrep: semgrep --config=p/security-audit to flag unsafe functions
- Coverity: Commercial tool with excellent C/C++ coverage

4. Follow CWE Guidelines

This vulnerability is classified under:
- CWE-120: Buffer Copy without Checking Size of Input
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer

Refer to CWE-120 for detailed guidance on prevention.

5. Use Secure C Library Functions

If available, use safer alternatives:
- POSIX: strlcpy(), strlcat() (if available on your platform)
- C11: memcpy_s() (MSVC) or equivalent
- Custom: Implement bounded string operations with clear semantics


Key Takeaways

  • Never trust tar file content: Header fields like name and linkname may not be null-terminated. Always use bounded operations.
  • strcpy() is a security liability: The function cannot prevent buffer overflow. Replace it with memcpy(dst, src, sizeof(dst)) followed by explicit null-termination.
  • sprintf() is similarly dangerous: Use snprintf(dst, sizeof(dst), fmt, ...) to prevent format string and buffer overflows.
  • Explicit null-termination is essential: When using memcpy(), always set the last byte to '\0' to ensure C string semantics.
  • Test with adversarial input: The regression test in this PR specifically exercises non-null-terminated buffers to catch regressions.

How Orbis AppSec Detected This

Source: Tar archive files processed by raw_to_header() and header_to_raw() functions, specifically the name and linkname fields from untrusted tar headers.

Sink: The vulnerable strcpy() calls at lines 114-115 in raw_to_header() and lines 131-132 in header_to_raw(), and sprintf() calls at lines 128-130.

Missing control: No bounds checking on the size of source data before copying into fixed-size destination buffers. No validation that tar header fields are null-terminated.

CWE: CWE-120 - Buffer Copy without Checking Size of Input

Fix: Replace all strcpy() calls with memcpy(dst, src, sizeof(dst)) followed by explicit null-termination (dst[sizeof(dst)-1] = '\0'). Replace all sprintf() calls with snprintf() specifying the destination buffer size.

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 in C string handling are a classic vulnerability, yet they continue to appear in production code. The fix is straightforward: always use bounded operations. By replacing strcpy() with memcpy() + explicit null-termination and sprintf() with snprintf(), the microtar library eliminated a critical attack surface.

The lesson applies broadly: whether you're parsing tar archives, processing HTTP headers, or handling any untrusted input, treat every string operation as a potential security boundary. Use the tools available to you—static analysis, compiler warnings, and security testing—to catch these issues before they reach production.

Secure coding practices like these are not optional extras; they're fundamental to building reliable, trustworthy software.


References

Frequently Asked Questions

What is a buffer overflow in C string handling?

A buffer overflow occurs when a function like strcpy() copies data into a fixed-size buffer without verifying the source fits within the destination bounds. If the source is longer than the buffer, it overwrites adjacent memory.

How do you prevent buffer overflow in C tar parsing?

Always use bounded string operations: memcpy(dst, src, sizeof(dst)) with explicit null-termination, or snprintf() instead of sprintf(). Validate input length before copying.

What CWE is buffer overflow in strcpy()?

CWE-120 (Buffer Copy without Checking Size of Input) and CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is using strncpy() enough to prevent buffer overflow?

strncpy() is safer than strcpy() but still problematic—it doesn't guarantee null-termination. The fix uses memcpy() with explicit null-termination, which is more reliable.

Can static analysis detect this buffer overflow?

Yes. Tools like Clang Static Analyzer, Coverity, and Semgrep can flag strcpy() usage and recommend bounded alternatives like memcpy() or snprintf().

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1118

Related Articles

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.

critical

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

A critical buffer overflow vulnerability was discovered in the ArrowTest() function in main/main.c, where sprintf() was writing formatted strings to a 24-byte buffer without bounds checking. By replacing sprintf() with snprintf() and specifying the buffer size, the vulnerability was eliminated, preventing attackers from corrupting heap memory through oversized width or height parameters.