Repository navigation
fix(file): resolve tar hard link targets from the archive root - #1510
Merged
TerryHowe merged 2 commits intoOct 8, 2026
Merged
Conversation
A tar hard link's Linkname is a path from the archive root, the same convention as the entry Name. extractTarDirectory resolved it against the directory of the link, so a hard link whose target sits in another directory failed to extract with "no such file or directory". The join came from the fix for GHSA-fxhp-mv3v-67qp, which was right to stop passing the raw relative path to os.Link but picked the symlink base. Resolve the target with resolveRelToBase against the extraction root, the same way entry names are resolved. The containment checks stay: the target must be inside the root, the symlink-escape check still runs on it, and a target that is itself a symlink is refused because link(2) may follow it on some platforms. Signed-off-by: Gaurav Jadhav <gaurav.vjadhav01@gmail.com>
jadhavgaurav
requested review from
TerryHowe,
Wwwsylvia,
sabre1041 and
shizhMSFT
as code owners
October 8, 2026 10:02
Codecov Reportโ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1510 +/- ##
==========================================
+ Coverage 91.50% 91.57% +0.07%
==========================================
Files 102 102
Lines 8099 8108 +9
==========================================
+ Hits 7411 7425 +14
+ Misses 677 672 -5
Partials 11 11 โ View full report in Codecov by Harness. ๐ New features to boost your workflow:
|
Member
|
Thanks! |
This was referenced Oct 13, 2026
Closed
TerryHowe
added a commit
that referenced
this pull request
Oct 14, 2026
#1572) A tar hard link's Linkname is a path from the archive root, the same convention as the entry Name. extractTarDirectory resolved it against the directory of the link, so a hard link whose target sits in another directory failed to extract with "no such file or directory". The join came from the fix for GHSA-fxhp-mv3v-67qp, which was right to stop passing the raw relative path to os.Link but picked the symlink base. Resolve the target with resolveRelToBase against the extraction root, the same way entry names are resolved. The containment checks stay: the target must be inside the root, the symlink-escape check still runs on it, and a target that is itself a symlink is refused because link(2) may follow it on some platforms. (cherry picked from commit ae73b33) Signed-off-by: Gaurav Jadhav <gaurav.vjadhav01@gmail.com> Signed-off-by: Terry Howe <terrylhowe@gmail.com> Co-authored-by: Gaurav Vijay Jadhav <gaurav.vjadhav01@gmail.com>
This was referenced Oct 16, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A tar hard link's
Linknameis a path from the archive root, the same convention as the entryName(GNU tar and bsdtar both write it that way, andtar tvfreportsdata/a/OWNERS link to data/b/OWNERS).extractTarDirectoryresolved it against the directory of the link instead. A hard link whose target is in a different directory therefore looked fordata/b/data/a/fileand the whole unpack failed withno such file or directory.The relative join came from the fix for GHSA-fxhp-mv3v-67qp. That fix was right to stop handing the raw relative path to
os.Link, which resolves against the process CWD, but it reused the symlink base. For hard links the base is the extraction root.Fixes #1499
Change
extractHardLinkresolvesLinknamewithresolveRelToBase(dirPath, dirName, linkname), the same call used for entry names, and joins the result ontodirPath. Symlink handling is unchanged.The containment checks are kept and one is added:
resolveRelToBaserejects a target outside the root (..components, a root-relative path that leavesdirName, an absolute path outsidedirPath) and a target reached through a symlinked directory.checkSymlinkEscaperuns on the resolved target, as it does for entry paths.link(2)may follow a symlink target on some platforms, so a pre-existing symlink in the extraction directory could otherwise be used to hard link a file from outside. A missing target now returns an error naming theLinkname.An absolute
LinknameinsidedirPathis still accepted, as before.Behavior note
A hard link
Linknamewritten relative to the link's own directory (for exampletest.txtfor a link in the same directory) was accepted before and is now read as an archive root path, so it is rejected unless it names a file at that root path. That form is not what tar tools produce, and it was the bug.Tests
utils_test.go:..components that stay inside the root, absolute path inside the root..out of the root (also from a nested directory),../outside.txt, absolute path outside the root, through a symlinked directory (relative and absolute symlink target), and a final component that is a symlink to a file outside the root (relative and absolute)file_test.go:TestStore_Dir_Push_HardLinkAcrossDirectoriespushes the layer from the issue throughStore.Pushand checksdata/b/fileis a hard link todata/a/file.Reverting only the body of
extractHardLinkto the old resolution makes the four positive unit tests and the store test fail, with the error from the issue. Removing the symlink-target check makes the two symlink-target escape cases fail.Checks
make testequivalent (go test ./... -racewith coverage, plus the encoding check) passes.go build ./...,go vet ./...andgofmt -lare clean.extractHardLinkis at 91.7% coverage.golangci-lintis not installed locally.The issue says
v2.6.2is affected too. I can send a backport tov2if you want one.