oss-sec mailing list archives
Re: GNU tar: listing/extraction desynchronization allows hidden file injection
From: Collin Funk <collin.funk1 () gmail com>
Date: Sat, 11 Apr 2026 21:10:20 -0700
Solar Designer <solar () openwall com> writes:
=== BUG #03 (HIGH) === Signed integer overflow in PAX v0.0/v0.1 sparse offset+numbytes Files: src/xheader.c:1426 (sparse_numbytes_decoder) src/xheader.c:1480 (sparse_map_decoder) Impact: Signed 64-bit integer overflow in offset+numbytes arithmetic. Undefined behavior that can corrupt internal state. Description: The PAX v1.0 decoder in sparse.c already checks: if (INT_ADD_OVERFLOW (sp.offset, u)) ... But the PAX v0.0 and v0.1 decoders in xheader.c do not. A crafted extended header with offset=9223372036854775800 and numbytes=100 passes individual range checks but overflows signed off_t when summed. UBSan output: xheader.c:1473: runtime error: signed integer overflow: 9223372036854775800 + 100 cannot be represented in type 'long int' Proposed fix: Add the same INT_ADD_OVERFLOW check after assigning numbytes in both sparse_numbytes_decoder() and sparse_map_decoder(), and return early with an error if the check fires.
Both of the functions they reference check that the value fits in off_t. A commit from 16 years ago shows the relevant code [1], but even before that overflows where checked.
=== BUG #04 (LOW) === FLEXNSIZEOF overflow in create_placeholder_file File: src/extract.c:1444 Impact: Theoretical heap buffer overflow if link_name is near SIZE_MAX. Description: create_placeholder_file() computes: xmalloc (FLEXNSIZEOF (struct delayed_link, target, strlen (current_stat_info.link_name) + 1)); If strlen(link_name) is near SIZE_MAX, adding 1 overflows size_t, and FLEXNSIZEOF computes a small allocation size. The subsequent strcpy writes past the buffer. PAX extended headers allow arbitrarily long linkpath values, so the attacker controls the length. In practice this requires the system to have nearly SIZE_MAX bytes of memory available, so it is mostly theoretical. Proposed fix: size_t link_len = strlen (current_stat_info.link_name); if (SIZE_MAX - sizeof (struct delayed_link) <= link_len) xalloc_die ();
I think it is safe to assume that no one has SIZE_MAX bytes of memory.
=== BUG #09 (MEDIUM) === strcmp overread past the non-NUL-terminated magic field File: src/list.c:565, 632 (read_header, decode_header) Impact: Buffer overread past the 6-byte magic field boundary. Format misdetection with crafted headers. Description: strcmp (h->magic, TMAGIC) // line 565 strcmp (header->header.magic, TMAGIC) // line 632 TMAGIC is "ustar\0" (6 bytes including NUL). The magic field in the header struct is exactly 6 bytes (char magic[6]). If the field contains "ustar\x01" (no NUL terminator), strcmp reads past byte 5 into the adjacent version[2] and uname[32] fields until it finds a NUL. ASan (with tight heap allocator) reports: ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 ... in __strcmp_sse42 Proposed fix: memcmp (h->magic, TMAGIC, sizeof TMAGIC)
This is just copied from Paul's commit in 2025 [2].
=== BUG #10 (MEDIUM) === Incomplete destructor for delayed_link_table hash entries File: src/extract.c:1478 (create_placeholder_file) Impact: Memory leak on hash collision/replacement. Description: hash_initialize (0, 0, dl_hash, dl_compare, free) The destructor callback is just free(), which frees the top-level struct delayed_link but not its sub-allocations: the sources linked list, cntx_name, acls_a_ptr, acls_d_ptr, and xattr_map. When a hash collision causes an old entry to be evicted, these are leaked. Proposed fix: Write a proper destructor: static void free_delayed_link (void *entry) { struct delayed_link *p = entry; struct string_list *s, *next; for (s = p->sources; s; s = next) { next = s->next; free (s); } free (p->cntx_name); free (p->acls_a_ptr); free (p->acls_d_ptr); xattr_map_free (&p->xattr_map); free (p); } And pass free_delayed_link instead of free to hash_initialize.
GNU tar never calls hash_free, and therefore will never call the free
function given to hash_initialize. It is explained in a comment and the
commit message [3]:
if (false)
{
/* There is little point to freeing, as we are about to exit,
and freeing is more likely to cause than cure trouble. */
hash_free (delayed_link_table);
delayed_link_table = NULL;
}
=== BUG #13 (LOW) === Integer truncation in decode_record File: src/xheader.c:630 (decode_record) Impact: Implicit narrowing from ptrdiff_t to int; UBSan -fsanitize=implicit-conversion fires. Description: int len_len = len_lim - p; // line 630 len_lim - p is ptrdiff_t. If the difference exceeds INT_MAX, the implicit conversion to int wraps to a negative value. The value is only used to format an error message, so the practical impact is limited to a garbled diagnostic, but UBSan rightfully flags it. Proposed fix: int len_len = (int) (len_lim - p < 1000 ? len_lim - p : 1000); This clamps the value to a reasonable range for the error message.
This proposal was just copied from Paul's commit in 2024 [4].
=== SUMMARY TABLE === # Severity File Function / Line Short description -- -------- ----------- --------------------------- -------------------------- 01 CRITICAL sparse.c:593 sparse_extract_file Archive stream desync -> file injection 14 CRITICAL (same root cause as #01) Full RCE chain demo 02 HIGH sparse.c:803 oldgnu_add_sparse Overlapping sparse regions -> corruption 03 HIGH xheader.c sparse_numbytes/map_decoder Missing INT_ADD_OVERFLOW 04 LOW extract.c:1444 create_placeholder_file FLEXNSIZEOF size_t overflow 05 MEDIUM extract.c:566 delay_set_stat Memory leak on reuse path 06 MEDIUM extract.c:996 apply_nonancestor_delayed.. Shallow xattr_map copy (UAF) 07 MEDIUM extract.c:985 apply_nonancestor_delayed.. Uninitialized tar_stat_info 08 MEDIUM extract.c:1898 apply_delayed_link Uninitialized tar_stat_info 09 MEDIUM list.c:565 read_header / decode_header strcmp past magic field 10 MEDIUM extract.c:1478 create_placeholder_file Incomplete hash destructor 11 LOW extract.c:364 check_time Signed overflow in time diff 12 LOW extract.c:953 apply_nonancestor_delayed.. file_name_len=0 underflow 13 LOW xheader.c:630 decode_record ptrdiff_t -> int truncation
I didn't look much at the others since I am not very familiar with tar. Hopefully Paul can quickly tell if they are bogus or not.
=== REPRODUCTION === All bugs were confirmed on tar 1.35 (commit 8e3c8fa17 from https://git.savannah.gnu.org/cgit/tar.git). Bugs #01, #02, and #14 are reproducible without sanitizers on any 64-bit Linux system. The remaining bugs require ASan, UBSan, or MSan to observe.
This commit hash does not exist, which does not inspire much confidence:
$ git log 8e3c8fa17
fatal: ambiguous argument '8e3c8fa17': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Collin
[1] https://git.savannah.gnu.org/cgit/tar.git/commit/?id=a59c819beb4886ee43f16dfd80ec1151fda1abe6
[2] https://git.savannah.gnu.org/cgit/tar.git/commit/?id=c11084bcc2d7d9976570a12263b81d2488066115
[3] https://git.savannah.gnu.org/cgit/tar.git/commit/?id=258d1c44e5ee7c58b28bf0000e9d737df6081885
[4] https://git.savannah.gnu.org/cgit/tar.git/commit/?id=d1e72a536f26188230a147d948b9057714fd0b6b
Current thread:
- GNU tar: listing/extraction desynchronization allows hidden file injection Alan Coopersmith (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Collin Funk (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Alan Coopersmith (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Solar Designer (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Collin Funk (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Paul Eggert (Apr 12)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Collin Funk (Apr 11)
- Re: GNU tar: listing/extraction desynchronization allows hidden file injection Collin Funk (Apr 11)
