The USTAR uname and gname fields are fixed 32-byte arrays with no
guaranteed NUL terminator.  read_in_tar_header() passed them directly
to getuidbyname()/getgidbyname(), which call getpwnam()/getgrnam() and
strlen() on the pointer.  A crafted ustar archive that fills uname,
gname, and the following header bytes with non-NUL data makes those
string functions read past the end of the 512-byte record (CWE-125).

The path is reachable from cpio -i and cpio -t on a crafted archive
unless -n is given, and the out-of-bounds bytes are used as the name
to resolve the UID/GID of extracted files.

Copy each field into a NUL-terminated buffer before it reaches any
string function, mirroring the existing stash_tar_linkname() idiom,
bounding the read to the field size.

* src/tar.c (tar_field_to_string): New function.
(read_in_tar_header): Use it for uname and gname before calling
getuidbyname/getgidbyname.

Signed-off-by: Sayed Kaif <[email protected]>
---
 src/tar.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/tar.c b/src/tar.c
index 493f299..288c03e 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -39,6 +39,21 @@ stash_tar_linkname (char *linkname)
   return hold_tar_linkname;
 }
 
+/* Copy a fixed-size tar header field FIELD of SIZE bytes into DST (which must
+   hold at least SIZE + 1 bytes) and NUL-terminate it.  The uname and gname
+   fields of a tar header are not guaranteed to be NUL-terminated in a
+   (possibly malicious) archive, so the raw field pointer must never be passed
+   to string functions such as getpwnam/getgrnam, which would read past the
+   end of the header.  Return DST.  */
+
+static char *
+tar_field_to_string (char *dst, char const *field, size_t size)
+{
+  memcpy (dst, field, size);
+  dst[size] = '\0';
+  return dst;
+}
+
 /* Try to split a long file name into prefix and suffix parts separated
    by a slash. Return the length of the prefix (not counting the slash). */
 
@@ -257,6 +272,8 @@ read_in_tar_header (struct cpio_file_stat *file_hdr, int 
in_des)
   struct tar_header *tar_hdr = (struct tar_header *) &tar_rec;
   uid_t *uidp;
   gid_t *gidp;
+  char uname[sizeof tar_hdr->uname + 1];
+  char gname[sizeof tar_hdr->gname + 1];
 
   tape_buffered_read ((char *) &tar_rec, in_des, TARRECORDSIZE);
 
@@ -321,13 +338,15 @@ read_in_tar_header (struct cpio_file_stat *file_hdr, int 
in_des)
      tar archives using the numeric UID/GID instead of the user/group
      names in /etc/passwd and /etc/groups.  (98/10/15) -BEM */
       if (archive_format == arf_ustar && !numeric_uid
-         && (uidp = getuidbyname (tar_hdr->uname)))
+         && (uidp = getuidbyname (tar_field_to_string (uname, tar_hdr->uname,
+                                                       sizeof 
tar_hdr->uname))))
        file_hdr->c_uid = *uidp;
       else
        file_hdr->c_uid = FROM_OCTAL (tar_hdr->uid);
 
       if (archive_format == arf_ustar && !numeric_uid
-         && (gidp = getgidbyname (tar_hdr->gname)))
+         && (gidp = getgidbyname (tar_field_to_string (gname, tar_hdr->gname,
+                                                       sizeof 
tar_hdr->gname))))
        file_hdr->c_gid = *gidp;
       else
        file_hdr->c_gid = FROM_OCTAL (tar_hdr->gid);
-- 
2.52.0.windows.1


Reply via email to