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++ 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.