Paul King created GROOVY-12359:
----------------------------------
Summary: deleteDir: treat Windows directory junctions as leaves,
as symbolic links already are
Key: GROOVY-12359
URL: https://issues.apache.org/jira/browse/GROOVY-12359
Project: Groovy
Issue Type: Improvement
Reporter: Paul King
Assignee: Paul King
Since GROOVY-12125, {{File.deleteDir()}} and the {{groovy-nio}}
{{Path.deleteDir()}} treat a symbolic link as a leaf: the link is removed, and
the directory it points at is not entered. The guard is
{{Files.isSymbolicLink}}, and its own comment records the gap this ticket
closes:
{code:java}
// never follow a symbolic link into its target; remove the link itself.
// Note: Files.isSymbolicLink does not detect Windows directory junctions
// (reparse points), which are therefore still traversed.
{code}
A junction inside a tree being deleted is followed, and the *target's* contents
are deleted. Two things make this the half of the link problem worth code
rather than documentation:
* creating a junction needs no privilege — {{mklink /J}} works for any user,
where Windows symbolic links require a privilege most users do not hold, so the
unprivileged local actor's tool is exactly the one the guard misses;
* it needs no timing — a junction is planted in advance and waits, unlike the
check-then-delete race, which requires writing into the tree concurrently
during deletion and is out of scope here (the threat model's local adversary is
not granted that; addressed separately).
h3. Proposed change
Replace the symlink-only test at both decision points (the root check and the
per-child check) in both implementations with an attribute read that refuses to
treat any reparse point as an ordinary directory:
{code:java}
BasicFileAttributes attrs = Files.readAttributes(path,
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
boolean leaf = attrs.isSymbolicLink() || attrs.isOther(); // symlink,
junction, or other reparse point
{code}
On POSIX, {{isOther()}} is false for ordinary files and directories, so
behaviour there does not change. A junction is then deleted as the link node it
is, matching the existing symlink behaviour.
h3. Verification
Junctions do not exist on POSIX, so this cannot be exercised on the development
machine; the expected {{isOther()}} attribute behaviour for a junction is
confirmed by the test rather than assumed in advance. The regression test is
Windows-gated: it creates a junction with {{cmd /c mklink /J}} — which succeeds
unprivileged on the CI runners — points it at a directory holding a marker
file, deletes the enclosing tree, and asserts the marker survives and the
junction node is gone. On other platforms the test is skipped; the existing
symlink tests from GROOVY-12125 continue to cover the POSIX side.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)