From: Todd C. Miller Subject: Re: PAX bug allows unprivileged user to disrupt backups To: Crystal Kolipe Cc: tech@openbsd.org Date: Sat, 28 Jun 2025 09:02:16 -0600 On Sun, 22 Jun 2025 04:38:25 -0300, Crystal Kolipe wrote: > It's possible for a non-root user with no special permissions to disrupt > backups made by root using tar and pax. Note that cpio is not affected. > > This is done by abusing file modification timestamps and fooling tar into > quitting early. The underlying issue is that pax was silently failing when writing out the extended headers if the mtime didn't fit in the ustar header for the extended header record. This should not be a fatal error since the extended header is not a real file and will only be extracted by versions of tar that doesn't support pax format. Instead of recomputing those header fields, we can use the fields from the underlying file when generating the extended header record. - todd Index: bin/pax/tar.c =================================================================== RCS file: /cvs/src/bin/pax/tar.c,v diff -u -p -u -r1.85 tar.c --- bin/pax/tar.c 17 Apr 2024 18:12:12 -0000 1.85 +++ bin/pax/tar.c 28 Jun 2025 14:52:04 -0000 @@ -71,9 +71,9 @@ static u_long tar_chksm(char *, int); static char *name_split(char *, int); static int ul_oct(u_long, char *, int, int); static int ull_oct(unsigned long long, char *, int, int); -static int rd_xheader(ARCHD *arcn, int, off_t); +static int rd_xheader(ARCHD *, int, off_t); #ifndef SMALL -static int wr_xheader(ARCHD *, struct xheader *); +static int wr_xheader(char *, HD_USTAR *, struct xheader *); #endif static uid_t uid_nobody; @@ -1037,7 +1037,7 @@ xheader_free(struct xheader *xhdr) } static int -wr_xheader(ARCHD *arcn, struct xheader *xhdr) +wr_xheader(char *fname, HD_USTAR *fhd, struct xheader *xhdr) { char hdblk[sizeof(HD_USTAR)]; HD_USTAR *hd; @@ -1064,16 +1064,18 @@ wr_xheader(ARCHD *arcn, struct xheader * * XXX dirname/basename portability (check return value?) */ (void)snprintf(buf, sizeof(buf), "%s/PaxHeaders.%ld/%s", - dirname(arcn->name), (long)getpid(), basename(arcn->name)); + dirname(fname), (long)getpid(), basename(fname)); fieldcpy(hd->name, sizeof(hd->name), buf, sizeof(buf)); - if (ul_oct(arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) || - ull_oct(arcn->sb.st_mtime < 0 ? 0 : arcn->sb.st_mtime, hd->mtime, - sizeof(hd->mtime), 1) || - ul_oct(arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) || - ul_oct(arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0)) - return -1; - + /* + * Inherit mode, mtime and owner from the file the headers are for. + * This will only be extracted as an actual file by implementations + * that don't support pax format. + */ + memcpy(hd->mode, fhd->mode, sizeof(hd->mode)); + memcpy(hd->mtime, fhd->mtime, sizeof(hd->mtime)); + memcpy(hd->uid, fhd->uid, sizeof(hd->uid)); + memcpy(hd->gid, fhd->gid, sizeof(hd->gid)); if (ul_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum, sizeof(hd->chksum), 3)) return -1; @@ -1337,7 +1339,7 @@ wr_ustar_or_pax(ARCHD *arcn, int ustar) if (!SLIST_EMPTY(&xhdr)) { int ret; - ret = wr_xheader(arcn, &xhdr); + ret = wr_xheader(arcn->name, hd, &xhdr); xheader_free(&xhdr); if (ret == -1) return(-1);