Skip to content

fix: reject Windows-unsafe names in archives (DOS devices, NTFS ADS) - #1091

Open
Mathjk wants to merge 3 commits into
ouch-org:mainfrom
Mathjk:fix/windows-reserved-names
Open

Mathjk wants to merge 3 commits into
ouch-org:mainfrom
Mathjk:fix/windows-reserved-names

Conversation

@Mathjk

@Mathjk Mathjk commented Sep 29, 2026

Copy link
Copy Markdown

Fixes #1090.

Bugs (Windows)

Two ways a crafted archive writes outside the intended destination:

  1. DOS device names: an entry named NUL/CON/COM1-COM9/LPT1-LPT9 (with or without extension — NUL.txt still resolves to the device) makes File::create open the device itself. Decompression streams attacker-controlled bytes into the device, then aborts mid-archive (os error 1), leaving the archive half-extracted. On hosts with a real COM/LPT/console this writes to hardware; reading CON during compression would hang on console input.
  2. NTFS ADS via :: an entry file:ads.txt silently writes a hidden alternate data stream file:ads.txt:$DATA — invisible to normal dir listings (verified via Get-Item -Stream *).

Fix (+181/-7)

New windows_unsafe_name_reason() in utils/fs.rs — per-component check for : (on raw encoded bytes, so non-UTF-8 names can't smuggle one past to_str()) and reserved device stems (stem before first ., case-insensitive, trailing-space tolerant — NUL .txt is caught too; nul2/COM10/.nul unaffected).

Wired through the existing shared validators:

  • validate_entry_path (tar, 7z, rar; zip too — applied to enclosed_name's output): zip/7z warn+skip the hostile entry (their unsafe-name convention — a hostile member no longer aborts the whole archive); tar/rar refuse with a clear error.
  • validate_symlink_target: symlink → NUL/ADS targets refused.
  • Compress side: walked files with device names warn+skip; explicit CLI args rejected up front.
  • tar: directory entries now validated before unpack_in (previously unvalidated on Windows).

Design choices: reject/skip, not rename (consistent with ouch's .. posture); cfg!(windows) runtime gate inside an always-compiled helper — zero #[cfg] at call sites and unit-testable on Linux CI. NUL and file:ads.txt remain legal on Linux — verified unchanged.

Verified on real Windows

Fixture Before After
zip mid-archive CON entry prior file written, then os error 1 abort, rest lost CON skipped w/ warning, rest extract
NUL/LPT1 in zip/tar/tar.gz os error 1 / device write clean refusal
file:ads.txt hidden ADS written refused — no ADS
symlink → NUL device-pointing symlink created refused
compress dir w/ real NUL,CON device open (CON hang) warn+skip, archive completes

cargo test 132/0 on Linux; Windows cross-build clean; fmt/clippy clean. list output intentionally unchanged (informational only).

On Windows, an archive entry whose final component is a DOS device name
(NUL, CON, COM1-9, LPT1-9, ..., with or without extension) makes
File::create open the device itself: decompression streams
archive-controlled bytes into the device and then aborts mid-archive.
A ':' in an entry name silently writes an NTFS alternate data stream,
hiding payload from normal file listings.

Funnel the check through the shared validators: validate_entry_path
(tar, 7z, rar; now also zip after enclosed_name) and
validate_symlink_target. Zip/7z skip the entry with a warning following
their unsafe-name convention; tar and rar refuse the archive.
Compression skips walked files with such names and rejects them as
explicit inputs so opening a device (e.g. reading CON hangs on console
input) cannot happen.

Both checks are Windows-only: NUL and ':' are legal file names
elsewhere, and behavior there is unchanged.
@valoq

valoq commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator
  • tar.rs: an unsafe entry aborts tar extraction and leaves partial output, while zip.rs:63 and sevenz.rs:48 skip it. Make them consistent
  • compress.rs:47: the output path is not checked
  • fs.rs:229: absolute symlink targets return before the device check at line 232.
  • zip.rs:66: the warning embeds the whole multi-line error and the output is garbled.
  • Only the helper is tested. No extract or compress test uses a reserved name.

@Mathjk

Mathjk commented Oct 8, 2026

Copy link
Copy Markdown
Author

Addressed in 68312f9 + a8b0dc5. Added check_entry_path returning a one-line reason; tar now warns+skips unsafe entries like zip/7z (no more abort mid-extraction), and all three warnings are single-line now. Output path is checked before the file is created, so ouch compress x NUL.tar never opens the device. The symlink device check runs before the absolute-path exemption. Windows integration tests added. rar left as-is — it aborts before any output lands.

@valoq

valoq commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

COM¹ COM² COM³ LPT¹ LPT² and LPT³ are also reserved as device names

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: archive entries named after DOS devices are opened as devices; colons write hidden NTFS streams

2 participants