Attachments:
  0001-Fix-out-of-bounds-read-in-name_match.patch
  0002-Fix-out-of-bounds-read-in-xattr_map_add.patch
  (optional) the small PoC archives / scripts described below

---

Hello,

While auditing GNU tar 1.35 I found two out-of-bounds heap reads. Both are
present in 1.35 and in current git master. Patches are attached (git
am-able,
one per bug); each is a minimal fix with a ChangeLog entry in the commit
message.

Please note I'm sending this to the public list; if you'd prefer to handle
either one privately first, let me know and I'll hold the details.


1) Out-of-bounds read in name_match (src/names.c)
-------------------------------------------------

name_match() reads file_name[cursor->length] without checking that the
member
name is at least cursor->length bytes long:

    if (cursor)
      {
        if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
            || cursor->found_count == 0)
          cursor->found_count++;

file_name is the archive member name; cursor->length is the length of a
command-line inclusion pattern. Under --wildcards an inclusion pattern can
match
a member name that is shorter than the pattern itself, so cursor->length can
exceed strlen(file_name) and the index reads past the end of the
heap-allocated
name. all_names_found() does the same probe but guards it with
"len >= cursor->length"; name_match() is missing that guard. (In git master
the
access has moved into register_match(), still unguarded.)

Reproduce (ASAN build), member named ".txt":

    tar --wildcards -tf poc.tar '*.txt'
    -> AddressSanitizer: heap-buffer-overflow READ, name_match at
names.c:1349,
       "0 bytes after 5-byte region"

The over-read distance is (pattern length - member-name length), so a long
pattern turns it into a crash on a stock build too. A single argv argument
is
capped at MAX_ARG_STRLEN, so the long pattern is given via --files-from:

    python3 -c 'open("p","w").write("a"+"*"*5000000+"\n")'   # member named
"a"
    tar --wildcards -T p -tf poc.tar
    -> Segmentation fault (core dumped)      # tar (GNU tar) 1.35

Impact: out-of-bounds read (CWE-125); denial of service (crash) when
listing or
extracting a crafted archive under --wildcards. Fix in 0001 (guard mirrors
all_names_found).


2) Out-of-bounds read in xattr_map_add (src/xattrs.c)
-----------------------------------------------------

xattr_map_add() copies len + 1 bytes from the caller's value buffer:

    p->xval_ptr = xmemdup (val, len + 1);     /* master: ximemdup (val, len
+ 1) */

In xattrs_xattrs_get() the value buffer is allocated to exactly asz bytes
(1024
in 1.35) and filled by lgetxattr/fgetxattr, which return -1/ERANGE only
when the
value is larger than the buffer. A value whose length is exactly asz
succeeds
and returns aret == asz; xattr values are raw bytes with no terminator. The
subsequent xmemdup(val, len + 1) then reads val[asz], one byte past the end.

Reproduce (ASAN build):

    mkdir t && : > t/f
    setfattr -n user.pwn -v "$(python3 -c 'import
sys;sys.stdout.write("A"*1024)')" t/f
    tar --xattrs -C t -cf /dev/null f
    -> AddressSanitizer: heap-buffer-overflow READ of size 1025 in xmemdup,
       from xattr_map_add (xattrs.c), "0 bytes after 1024-byte region"
       (use a 1536-byte value against git master, whose buffer starts at
1536)

Impact: out-of-bounds read (CWE-125) when creating an archive with --xattrs
over
a file carrying a crafted extended attribute. This one is a 1-byte
over-read and
I have not turned it into more than that; fix in 0002 copies len bytes and
terminates the stored copy, matching how the xattr/ACL decoders already
handle
their values.


I can send the small PoC archives / a short script that builds them if
useful.
If these warrant CVE IDs, I'm happy to request them from MITRE referencing
this
thread, unless you'd rather coordinate that yourself.

Thanks for maintaining tar,
Vladimir Akparov
From d552d36fe7d342ebdcd6766f8dbb4a1c6248840b Mon Sep 17 00:00:00 2001
From: Your Name <[email protected]>
Date: Fri, 6 Nov 2026 17:15:28 +0300
Subject: [PATCH 1/2] Fix out-of-bounds read in name_match

name_match indexed file_name[cursor->length] without checking that the
member name is at least cursor->length bytes long.  Under --wildcards an
inclusion pattern can match a member name shorter than the pattern, so the
index read past the end of the heap-allocated name.  With a long pattern
(e.g. supplied via --files-from, which is not subject to the single-argument
length limit) the read reaches unmapped memory and crashes tar.  Guard the
access with a length check, as all_names_found already does.

* src/names.c (name_match): require strlen (file_name) >= cursor->length
before reading file_name[cursor->length].
---
 src/names.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/names.c b/src/names.c
index d302737..74e33b4 100644
--- a/src/names.c
+++ b/src/names.c
@@ -1346,7 +1346,8 @@ name_match (const char *file_name)
 	}
       if (cursor)
 	{
-	  if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
+	  if (!(strlen (file_name) >= cursor->length
+		&& ISSLASH (file_name[cursor->length]) && recursion_option)
 	      || cursor->found_count == 0)
 	    cursor->found_count++; /* remember it matched */
 	  chdir_do (cursor->change_dir);
-- 
2.43.0

From 66953aaf0bcb3c7f6bd0c43a432e8a102194fb85 Mon Sep 17 00:00:00 2001
From: Your Name <[email protected]>
Date: Fri, 6 Nov 2026 17:15:28 +0300
Subject: [PATCH 2/2] Fix out-of-bounds read in xattr_map_add

xattr_map_add copied len + 1 bytes with xmemdup from a buffer that holds
only len valid bytes.  When archiving with --xattrs, xattrs_xattrs_get
fills an exactly-sized value buffer with lgetxattr/fgetxattr, which do not
NUL-terminate the value; a value whose length equals the buffer size then
made the copy read one byte past the end of the allocation.  Copy len bytes
and terminate the stored copy ourselves.

* src/xattrs.c (xattr_map_add): allocate len + 1, memcpy len bytes and set
the terminating NUL, instead of xmemdup (val, len + 1).
---
 src/xattrs.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/xattrs.c b/src/xattrs.c
index 86a7e59..2fe2811 100644
--- a/src/xattrs.c
+++ b/src/xattrs.c
@@ -75,7 +75,13 @@ xattr_map_add (struct xattr_map *map,
 			      sizeof (map->xm_map[0]));
   p = &map->xm_map[map->xm_size];
   p->xkey = xstrdup (key);
-  p->xval_ptr = xmemdup (val, len + 1);
+  /* Copy exactly LEN bytes from the caller's buffer and NUL-terminate the
+     stored copy ourselves; the source is not guaranteed to have a readable
+     byte at val[len] (e.g. a raw xattr value exactly filling the get buffer),
+     so xmemdup(val, len + 1) would read one byte past the end.  */
+  p->xval_ptr = xmalloc (len + 1);
+  memcpy (p->xval_ptr, val, len);
+  p->xval_ptr[len] = '\0';
   p->xval_len = len;
   map->xm_size++;
 }
-- 
2.43.0

Reply via email to