Skip to content

Windows: zstd -r follows directory junctions and symlinks, so -r --rm deletes files outside the given folder #4853

Closed

Description

@ijbranch

Describe the bug
On Windows, recursive mode ignores reparse points. UTIL_isLink() (programs/util.c) returns 0 on every
non-POSIX platform, and the Windows UTIL_prepareFileList() never checks FILE_ATTRIBUTE_REPARSE_POINT, so a
directory junction or directory symlink inside the tree is entered like an ordinary folder. Unlike the POSIX
build, --no-follow-style behaviour (followLinks = 0, the default for -r) has no effect.

Consequences:

  • zstd -r --rm folder compresses and deletes files that are not under folder at all.
  • A junction pointing at an ancestor recurses until the path length or the stack gives out.

To Reproduce (Windows 11, cmd; mklink /J needs no administrator rights)

> mkdir outside tree
> echo outside the tree> outside\secret.txt
> echo inside> tree\a.txt
> mklink /J tree\link outside
Junction created for tree\link <<===>> outside
> zstd -r --rm -q tree
> dir /s /b outside tree
...\outside\secret.txt.zst
...\tree\a.txt.zst

outside\secret.txt has been compressed and deleted, although only tree was named.

Expected behavior
As on POSIX with the default followLinks = 0: junctions and directory symlinks met during recursion are skipped
(with a message at a suitable display level).

Suggested fix
In the Windows UTIL_prepareFileList(), skip entries whose cFile.dwFileAttributes include
FILE_ATTRIBUTE_REPARSE_POINT when followLinks is 0, and make UTIL_isLink() test that attribute
(GetFileAttributesA) on Windows. (-f sets followLinks = 1, as on POSIX; the reproduction above does not use
-f.)

Desktop

  • OS: Windows 11 Pro (26200), x64
  • Version: v1.5.7; UTIL_isLink() unchanged on dev (2026-10-05). UTIL_prepareFileList() on dev differs only
    by a free(path) leak fix
  • Compiler: MSVC 14.51 (Visual Studio 2026 Build Tools), Release
  • Build system: build/cmake, Visual Studio generator

Environment caveats

  • Found by code review, then reproduced as shown on Windows 11 only (x64), with zstd.exe built from the v1.5.7 source by MSVC 14.51 (Visual Studio 2026 Build Tools) through build/cmake, Win64 Release only. Not tested with MinGW, clang-cl or the official release binaries, nor on Win32.
  • tests/playTests.sh was not run (no POSIX shell environment here).
  • The functions cited were compared with dev (2026-10-05) by source and are unchanged, but dev itself was not built.
  • The analysis was AI-assisted (Claude); every behaviour reported above was reproduced as shown.

Activity

  1. self-assigned this
    on Nov 8, 2026
  2. Cyan4973 commented on Nov 8, 2026

    @Cyan4973
    Contributor

    Thanks for the detailed report and the reproduction steps.
    The issue is being fixed by #4905, which skips symbolic links and junctions during -r on Windows unless -f is used, as on POSIX.
    You are welcome to test it to check if it fixes your issue correctly.

  3. added a commit that references this issue on Nov 8, 2026
    6bdc1d3
  4. ijbranch commented on Nov 8, 2026

    @ijbranch
    Author

    Thanks for the quick fix. I tested it on Windows against the reproduction from the report, and it works.

    Setup: Windows 11 Pro (build 26300), MSVC 14.51 (Visual Studio 2026 Build Tools), x64 Release, built with build/cmake (legacy support off). Two builds: dev at 4b0e377, which includes 6bdc1d3, and v1.5.7 as the control (its programs/ is identical to the release).

    Each case ran in a fresh directory: tree\inside.txt, outside\secret.txt and tree\link pointing to outside.

    Case v1.5.7 dev (4b0e377)
    zstd -r --rm -q tree, tree\link a junction (mklink /J) outside\secret.txt compressed and deleted outside\secret.txt untouched; tree\inside.txt compressed
    Same, tree\link a directory symlink (mklink /D) compressed and deleted untouched
    Same with -f compressed and deleted compressed and deleted, as documented for -f
    zstd -r -k -q tree with tree\loop a junction to tree itself recursed until the path was too long (Cannot open directory 'tree\loop\loop\...'), exit 1 a.txt.zst written once, exit 0

    Without -q, dev prints Warning : tree\link is a symbolic link, ignoring for a junction as well.

    Not covered: a Win32 build, playTests.sh (including the test from #4860), and non-name-surrogate reparse points such as OneDrive placeholders or deduplicated files, which I did not have available.

    Tested with the help of an AI assistant (Claude Code).

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions