This is a short story about how dangerous a trivial tar unpacking might be, and what can be done to minimize the risk or completely avoid it.
In the seconds that followed, I noticed the rapid decline of my system. The windows of my XFCE session stopped redrawing, the X server itself shut down. I couldn't run sudo. I couldn't even boot my system again. It happened so quickly and unexpectedly that I could hardly believe that my last command had caused the crash. Fortunately, booting in a single mode and detailed analysis of the tar archive revealed the root cause.
But not knowing that, I quickly figured out that all the tools are conveniently located under a relative path "./usr/bin/", so my first thought was that I'll simply extract them to my root directory and they'll be immediately available in my $PATH. So, I extracted it as root:
I got so used to having 0:0 as a user:group that I didn't even check the content of the file. Or, even what permissions were set. Frankly, I wasn't even aware of the hidden problem of having a current directory in the archive at the time. I just looked at the directory structure, and that was a huge mistake. Because if I checked them, I'd see non-standard permissions (700) of a current directory ".", and non-standard user and group of the entire archive content:
Recently, I was practicing an installation of Void Linux via chroot using XBPS method . I needed the XBPS Package Manager installed on my Fedora Linux host to prepare Void Linux's base system. One of the options is to download an archive of statically built tools from the official repository. I chose https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz
For this little demo, I spun up a new VM. Don't try this on your running system!
The tar archive contains the current directory "./", which became the root directory when I changed it with "tar -C / ..." to change it before extracting. Restoring the owner and permissions of the current (top) directory of the archive resulted in setting 700 permissions and 2002:2000 as owner:group on my directory tree, which changed its expected state. Thus, my own user completely lost access to the entire file system. Who could have expected that? ;)
What can be done to prevent it?
In general, it is convenient to create a new archive with a relative directory tree using a command similar to
$ tar -C /path/to/rootfs -czf myarchive.tar.gz .
because you don't have to worry about the internal directory structure, and it's just one command. All files are addressed with simple ".". It is also useful during extraction, since "-C /some/path/" allows you to choose any destination directory. On the other hand, this approach adds a current directory to the archive (the top one in the output above), which takes away all convenience. For example, if an archive contains a backup of users' home directories with all the necessary permissions, it could be super easy to restore them by running something like "tar -C /home -xpf homes.tar.gz". But this only works if the archive doesn't contain a current directory and the target "/home/" is not modified.
There are a few ways to create an archive without a current directory, but most of them require either a directory change beforehand, or defining all files/directories for the future archive. However, I found a way that, although it looks odd, does the job in one command:
$ tar --transform = 's|tmp/rootfs|.|' --show-transformed-names -cvf myarchive.tar /tmp/rootfs/* # or without a verbose mode $ tar --transform = 's|tmp/rootfs|.|' -cf myarchive.tar /tmp/rootfs/*
Thanks to Eric Radman for pointing out that BSD tar has another option, -s, for similar functionality.
Another and pretty typical way to create such archives with some tools is to use fakeroot. It runs as an unprivileged user and pretends that all files are owned by root. In fact, it's just an illusion. Let's have a look at the directory with the extracted original xbps tools:
$ tree -gpu xbps-tools/ | head [ drwxr-xr-x 2002 2000 ] xbps-tools/ ├── [ drwxr-xr-x 2002 2000 ] usr │ └── [ drwxr-xr-x 2002 2000 ] bin │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-alternatives -> xbps-alternatives.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-alternatives.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-checkvers -> xbps-checkvers.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-checkvers.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-create -> xbps-create.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-create.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-dgraph -> xbps-dgraph.static
And this is how it looks under fakeroot
$ fakeroot /bin/bash root@localhost> tree -gpu xbps-tools/ | head [ drwxr-xr-x root root ] xbps-tools/ ├── [ drwxr-xr-x root root ] usr │ └── [ drwxr-xr-x root root ] bin │ ├── [ lrwxrwxrwx root root ] xbps-alternatives -> xbps-alternatives.static │ ├── [ -rwxr-xr-x root root ] xbps-alternatives.static │ ├── [ lrwxrwxrwx root root ] xbps-checkvers -> xbps-checkvers.static │ ├── [ -rwxr-xr-x root root ] xbps-checkvers.static │ ├── [ lrwxrwxrwx root root ] xbps-create -> xbps-create.static │ ├── [ -rwxr-xr-x root root ] xbps-create.static │ ├── [ lrwxrwxrwx root root ] xbps-dgraph -> xbps-dgraph.static
This fake environment allows you to create a tar archive with files owned by root without changing their real owners.
One more nice solution is to use the cpio tool to create or extract POSIX tar archives. This format can be enabled during archive creation by adding "-H ustar". However, during extraction, the format is automatically detected, and it also doesn't change the permissions of the current directory, even if it exists in the archive! If you add the "-d" option and run cpio with sudo, all non-existing subdirectories will be created as root:root, which is also very convenient.
$ tree -gpu newroot/ [ drwxr-xr-x root root ] newroot/ $ xz -cd xbps-static-latest.x86_64-musl.tar.xz | sudo cpio -D newroot -idv . ./usr ./usr/bin ./usr/bin/xbps-uunshare ./usr/bin/xbps-uhelper ./usr/bin/xbps-uchroot ./usr/bin/xbps-rindex ./usr/bin/xbps-remove ./usr/bin/xbps-reconfigure ./usr/bin/xbps-query ./usr/bin/xbps-pkgdb ./usr/bin/xbps-install ./usr/bin/xbps-fetch ./usr/bin/xbps-fbulk ./usr/bin/xbps-digest ./usr/bin/xbps-dgraph ./usr/bin/xbps-create ./usr/bin/xbps-checkvers ./usr/bin/xbps-alternatives ./usr/bin/xbps-alternatives.static ./usr/bin/xbps-checkvers.static ./usr/bin/xbps-create.static ./usr/bin/xbps-dgraph.static ./usr/bin/xbps-digest.static ./usr/bin/xbps-fbulk.static ./usr/bin/xbps-fetch.static ./usr/bin/xbps-install.static ./usr/bin/xbps-pkgdb.static ./usr/bin/xbps-query.static ./usr/bin/xbps-reconfigure.static ./usr/bin/xbps-remove.static ./usr/bin/xbps-rindex.static ./usr/bin/xbps-uchroot.static ./usr/bin/xbps-uhelper.static ./usr/bin/xbps-uunshare.static ./var ./var/db ./var/db/xbps ./var/db/xbps/keys ./var/db/xbps/keys/60:ae:0c:d6:f0:95:17:80:bc:93:46:7a:89:af:a3:2d.plist ./var/db/xbps/keys/3d:b9:c0:50:41:a7:68:4c:2e:2c:a9:a2:5a:04:b7:3f.plist 179893 blocks $ tree -gpu newroot/ | head [ drwxr-xr-x root root ] newroot/ ├── [ drwxr-xr-x 2002 2000 ] usr │ └── [ drwxr-xr-x 2002 2000 ] bin │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-alternatives -> xbps-alternatives.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-alternatives.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-checkvers -> xbps-checkvers.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-checkvers.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-create -> xbps-create.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-create.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-dgraph -> xbps-dgraph.static
Note that newroot/ was left untouched and is still owned by root:root with 755 permissions. But cpio is even cooler. You can create a POSIX tar and easily control which files go in it, because cpio only accepts filenames. So you can get the file list with find and then filter the output to remove (for this particular example) /usr, /usr/bin, /var/, /var/db, and that's it. Super safe and convenient for everyone, while maintaining a relative directory structure inside. Here is an example of how I created a tar archive with cpio, without any "systems" directories, and then extracted it with tar in the usual way:
# Create a tar archive with 'cpio' of previously unpacked xbps tools $ ( cd xbps-tools && find . | grep -v -e '^\.$' -e '^\./usr$' -e '^\./usr/bin$' -e '^\./var$' -e '^\./var/db$' | cpio -ov -H ustar > ../myxbps.tar ) ./var/db/xbps/ ./var/db/xbps/keys/ ./var/db/xbps/keys/3d:b9:c0:50:41:a7:68:4c:2e:2c:a9:a2:5a:04:b7:3f.plist ./var/db/xbps/keys/60:ae:0c:d6:f0:95:17:80:bc:93:46:7a:89:af:a3:2d.plist ./usr/bin/xbps-uunshare.static ./usr/bin/xbps-uhelper.static ./usr/bin/xbps-uchroot.static ./usr/bin/xbps-rindex.static ./usr/bin/xbps-remove.static ./usr/bin/xbps-reconfigure.static ./usr/bin/xbps-query.static ./usr/bin/xbps-pkgdb.static ./usr/bin/xbps-install.static ./usr/bin/xbps-fetch.static ./usr/bin/xbps-fbulk.static ./usr/bin/xbps-digest.static ./usr/bin/xbps-dgraph.static ./usr/bin/xbps-create.static ./usr/bin/xbps-checkvers.static ./usr/bin/xbps-alternatives.static ./usr/bin/xbps-alternatives ./usr/bin/xbps-checkvers ./usr/bin/xbps-create ./usr/bin/xbps-dgraph ./usr/bin/xbps-digest ./usr/bin/xbps-fbulk ./usr/bin/xbps-fetch ./usr/bin/xbps-install ./usr/bin/xbps-pkgdb ./usr/bin/xbps-query ./usr/bin/xbps-reconfigure ./usr/bin/xbps-remove ./usr/bin/xbps-rindex ./usr/bin/xbps-uchroot ./usr/bin/xbps-uhelper ./usr/bin/xbps-uunshare 179889 blocks $ file myxbps.tar myxbps.tar: POSIX tar archive # Check with 'tar' that all files have non root user/group and the archive doesn't contain . /usr /usr/bin /var /var/db $ tar -tvf myxbps.tar drwxr-xr-x 2002 /2000 0 2024 -05-21 16 :04 var/db/xbps/ drwxr-xr-x 2002 /2000 0 2024 -05-21 16 :04 var/db/xbps/keys/ -rw-r--r-- 2002 /2000 1410 2024 -05-21 16 :04 var/db/xbps/keys/3d:b9:c0:50:41:a7:68:4c:2e:2c:a9:a2:5a:04:b7:3f.plist -rw-r--r-- 2002 /2000 1410 2024 -05-21 16 :04 var/db/xbps/keys/60:ae:0c:d6:f0:95:17:80:bc:93:46:7a:89:af:a3:2d.plist -rwxr-xr-x 2002 /2000 5623104 2024 -05-21 16 :04 usr/bin/xbps-uunshare.static -rwxr-xr-x 2002 /2000 5643584 2024 -05-21 16 :04 usr/bin/xbps-uhelper.static -rwxr-xr-x 2002 /2000 5631296 2024 -05-21 16 :04 usr/bin/xbps-uchroot.static -rwxr-xr-x 2002 /2000 6414144 2024 -05-21 16 :04 usr/bin/xbps-rindex.static -rwxr-xr-x 2002 /2000 5779264 2024 -05-21 16 :04 usr/bin/xbps-remove.static -rwxr-xr-x 2002 /2000 5643904 2024 -05-21 16 :04 usr/bin/xbps-reconfigure.static -rwxr-xr-x 2002 /2000 5685440 2024 -05-21 16 :04 usr/bin/xbps-query.static -rwxr-xr-x 2002 /2000 5643904 2024 -05-21 16 :04 usr/bin/xbps-pkgdb.static -rwxr-xr-x 2002 /2000 5787648 2024 -05-21 16 :04 usr/bin/xbps-install.static -rwxr-xr-x 2002 /2000 5639488 2024 -05-21 16 :04 usr/bin/xbps-fetch.static -rwxr-xr-x 2002 /2000 5631296 2024 -05-21 16 :04 usr/bin/xbps-fbulk.static -rwxr-xr-x 2002 /2000 5623104 2024 -05-21 16 :04 usr/bin/xbps-digest.static -rwxr-xr-x 2002 /2000 5640384 2024 -05-21 16 :04 usr/bin/xbps-dgraph.static -rwxr-xr-x 2002 /2000 6402240 2024 -05-21 16 :04 usr/bin/xbps-create.static -rwxr-xr-x 2002 /2000 5644032 2024 -05-21 16 :04 usr/bin/xbps-checkvers.static -rwxr-xr-x 2002 /2000 5643904 2024 -05-21 16 :04 usr/bin/xbps-alternatives.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-alternatives -> xbps-alternatives.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-checkvers -> xbps-checkvers.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-create -> xbps-create.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-dgraph -> xbps-dgraph.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-digest -> xbps-digest.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-fbulk -> xbps-fbulk.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-fetch -> xbps-fetch.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-install -> xbps-install.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-pkgdb -> xbps-pkgdb.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-query -> xbps-query.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-reconfigure -> xbps-reconfigure.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-remove -> xbps-remove.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-rindex -> xbps-rindex.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-uchroot -> xbps-uchroot.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-uhelper -> xbps-uhelper.static lrwxrwxrwx 2002 /2000 0 2024 -05-21 16 :04 usr/bin/xbps-uunshare -> xbps-uunshare.static # Created a new directory to emulate a root file system $ tree -gpu newroot2/ [ drwxr-xr-x root root ] newroot2/ ├── [ drwxr-xr-x root root ] usr │ └── [ drwxr-xr-x root root ] bin └── [ drwxr-xr-x root root ] var └── [ drwxr-xr-x root root ] db # Extract with 'tar' in a usual way $ sudo tar -C newroot2 -xvf myxbps.tar var/db/xbps/ var/db/xbps/keys/ var/db/xbps/keys/3d:b9:c0:50:41:a7:68:4c:2e:2c:a9:a2:5a:04:b7:3f.plist var/db/xbps/keys/60:ae:0c:d6:f0:95:17:80:bc:93:46:7a:89:af:a3:2d.plist usr/bin/xbps-uunshare.static usr/bin/xbps-uhelper.static usr/bin/xbps-uchroot.static usr/bin/xbps-rindex.static usr/bin/xbps-remove.static usr/bin/xbps-reconfigure.static usr/bin/xbps-query.static usr/bin/xbps-pkgdb.static usr/bin/xbps-install.static usr/bin/xbps-fetch.static usr/bin/xbps-fbulk.static usr/bin/xbps-digest.static usr/bin/xbps-dgraph.static usr/bin/xbps-create.static usr/bin/xbps-checkvers.static usr/bin/xbps-alternatives.static usr/bin/xbps-alternatives usr/bin/xbps-checkvers usr/bin/xbps-create usr/bin/xbps-dgraph usr/bin/xbps-digest usr/bin/xbps-fbulk usr/bin/xbps-fetch usr/bin/xbps-install usr/bin/xbps-pkgdb usr/bin/xbps-query usr/bin/xbps-reconfigure usr/bin/xbps-remove usr/bin/xbps-rindex usr/bin/xbps-uchroot usr/bin/xbps-uhelper usr/bin/xbps-uunshare $ tree -gpu newroot2/ [ drwxr-xr-x root root ] newroot2/ ├── [ drwxr-xr-x root root ] usr │ └── [ drwxr-xr-x root root ] bin │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-alternatives -> xbps-alternatives.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-alternatives.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-checkvers -> xbps-checkvers.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-checkvers.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-create -> xbps-create.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-create.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-dgraph -> xbps-dgraph.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-dgraph.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-digest -> xbps-digest.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-digest.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-fbulk -> xbps-fbulk.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-fbulk.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-fetch -> xbps-fetch.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-fetch.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-install -> xbps-install.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-install.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-pkgdb -> xbps-pkgdb.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-pkgdb.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-query -> xbps-query.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-query.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-reconfigure -> xbps-reconfigure.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-reconfigure.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-remove -> xbps-remove.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-remove.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-rindex -> xbps-rindex.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-rindex.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-uchroot -> xbps-uchroot.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-uchroot.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-uhelper -> xbps-uhelper.static │ ├── [ -rwxr-xr-x 2002 2000 ] xbps-uhelper.static │ ├── [ lrwxrwxrwx 2002 2000 ] xbps-uunshare -> xbps-uunshare.static │ └── [ -rwxr-xr-x 2002 2000 ] xbps-uunshare.static └── [ drwxr-xr-x root root ] var └── [ drwxr-xr-x root root ] db └── [ drwxr-xr-x 2002 2000 ] xbps └── [ drwxr-xr-x 2002 2000 ] keys ├── [ -rw-r--r-- 2002 2000 ] 3d:b9:c0:50:41:a7:68:4c:2e:2c:a9:a2:5a:04:b7:3f.plist └── [ -rw-r--r-- 2002 2000 ] 60 :ae:0c:d6:f0:95:17:80:bc:93:46:7a:89:af:a3:2d.plist
Note that all "system" directories such as /usr or /var/db are left unmodified with their original owners and permissions. That's how I would create such archives with files to be extracted to the root filesystem.