-
Notifications
You must be signed in to change notification settings - Fork 206
NTFS Junctions should be serialised with SubsituteName instead of PrintName #340
Description
If a junction is created with New-Item in PowerShell, the PrintName won't be set. The SubstitueName is ignored when creating the container layer, and this results in a corrupt junction.
Steps to reproduce
(I'm using BuildKit, containerd, and nerdctl on Windows with an install process adapted from the BuildKit docs, but I suspect it would do the same thing on Docker too.)
# escape=`
FROM mcr.microsoft.com/windows/servercore:ltsc2025
WORKDIR C:\test
RUN mkdir tgt `
&& powershell -c "New-Item -ItemType Symboliclink -Path ps-sym -Target tgt" `
&& powershell -c "New-Item -ItemType Junction -Path ps-jun -Target tgt" `
&& mklink /j cmd-jun tgt `
&& mklink /d cmd-sym tgt `
&& powershell -c "Get-ChildItem | Where-Object { $_.LinkType } | ForEach-Object { echo \"QUERY FOR $($_.FullName)\"; fsutil reparsepoint query $_.FullName; echo '-------' }" `
&& dir
RUN powershell -c "Get-ChildItem | Where-Object { $_.LinkType } | ForEach-Object { echo \"QUERY FOR $($_.FullName)\"; fsutil reparsepoint query $_.FullName; echo '-------' }" `
&& dir
# Prevent actually building image
RUN exit 1
You will notice that there is no PrintName set (it's empty) in the PowerShell-created junction, whereas mklink in cmd does set it.
In the second RUN step, you can see that the SubstituteName is lost on the Powershell-created junction, but it should be retained.
Cause
According to Claude AI, here's the exact problematic code in reparse.go, the decodeWindowsReparsePointData function:
func decodeWindowsReparsePointData(b []byte, isMountPoint bool) (*ReparsePoint, error) {
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) // ← PrintNameOffset
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8]) // ← PrintNameLength
name := make([]uint16, nameLength/2)
// ...
}
Based on the AI response, I'm assuming this is called by https://github.com/microsoft/hcsshim/ which is in turn used by containerd/BuildKit.
Other
I'm not actually sure if PrintName is meant to be optional or required. Clearly PowerShell isn't setting it, nor is it present in some Rust libraries (see astral-sh/uv#17966), but perhaps future versions of PowerShell should set it? I can raise an issue on the PowerShell repo, but I don't think v5 (which is the only version available in Windows containers) will receive any such fixes, so a workaround is probably still necessary here.
(Also in PowerShell 7, it will refuse to accept a relative path as the target for a junction in New-Item, rather than silently converting it, so you'd have to use the value (Get-Item tgt).FullName.)