Skip to content

Confine symlinks to root during Windows unpack - #33

Closed
rzlink wants to merge 1 commit into
moby:mainfrom
rzlink:wcow-symlink-scope-confine
Closed

Confine symlinks to root during Windows unpack#33
rzlink wants to merge 1 commit into
moby:mainfrom
rzlink:wcow-symlink-scope-confine

Conversation

@rzlink

@rzlink rzlink commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Windows has no chroot, so chrootarchive unpacks inline against the real filesystem (see chrootarchive/archive_windows.go). As a result, an archive symlink whose target escapes the extraction root is rejected outright by the strings.HasPrefix(targetPath, extractDir) check in createTarFile. That breaks ADD <tar> / style builds on Windows that Linux handles fine, because on Linux the same unpack runs inside a chroot that simply contains the escaping symlink.

This is the BuildKit/Windows-containers manifestation of moby/moby#47107 (TestBuildSymlinkBreakout, currently skip.If(UsingSnapshotter, ...)).

Approach

Add an opt-in TarOptions.ConfineSymlinksToRoot flag:

  • Set only by chrootarchive's Windows invokeUnpack. It scope-confines symlink resolution to the extraction root via a new secureJoinScope helper (traversal semantics ported from containerd/continuity fs.RootPath): an escaping symlink is created rather than rejected, and any entry written through it resolves back inside the root instead of breaking out — the Windows analogue of the containment the Linux path gets from chroot.
  • Plain archive.Unpack/Untar are unchanged: without the flag they still reject escaping symlinks, so the existing TestUntarInvalidSymlink / TestApplyLayerInvalidSymlink breakout guarantees are fully preserved.

Tests

  • New chrootarchive/archive_windows_test.go: TestUntarSymlinkScopeConfine — an escaping symlink (/../../.. chain) plus a file written through it: asserts the unpack succeeds, the symlink is created, the file is contained at the extraction root, and nothing escapes above it.
  • All existing breakout tests (TestUntarInvalidSymlink, TestUntarInvalidHardlink, TestApplyLayerInvalid*, TestUntarHardlinkToSymlink) continue to pass on Windows.

Validated end-to-end on a Windows containerd/BuildKit worker: ADD symlink.tar / (escaping symlink) that previously failed with invalid symlink ... now builds successfully with the file contained.

Release notes

Confine symlink resolution to the extraction root when unpacking archives on Windows, so archives containing root-escaping symlinks extract with the same containment as the Linux chroot path.

Windows has no chroot, so chrootarchive unpacks inline against the real
filesystem. As a result an archive symlink whose target escapes the
extraction root was rejected outright, which breaks `ADD <tar> /` builds
that Linux handles fine inside its chroot (moby/moby#47107).

Add a TarOptions.ConfineSymlinksToRoot flag, set by chrootarchive's
Windows invokeUnpack, that scope-confines symlink resolution to the
extraction root (secureJoinScope). With it, an escaping symlink is
created rather than rejected, and any entry written through it resolves
back inside the root instead of breaking out - the Windows analogue of
the containment the Linux path gets from chroot.

Plain archive.Unpack is unchanged: without the flag it still rejects
escaping symlinks, so the existing TestUntarInvalidSymlink breakout
guarantees are preserved.

Signed-off-by: Dawei Wei <wei.dawei.cn@gmail.com>
@rzlink

rzlink commented Jul 7, 2026

Copy link
Copy Markdown
Author

Closing: superseded by the #24/#25/#26 direction. Keeping the branch for reference while the clamp-vs-refuse semantics are settled.

@rzlink rzlink closed this Jul 7, 2026
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.

1 participant