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
