vendor: github.com/moby/go-archive v0.3.2 - #53261
Conversation
|
Verified that the reproducer from passes; #53258 (comment) ./repro.sh
Narrow archive contents:
.git/
.git/HEAD
CONTROL PASS: narrow-root request returned HTTP 200
CONTROL PASS: narrow-root variant created the expected file
Broad archive contents:
var/run/act/actions/broad/.git/
var/run/act/actions/broad/.git/HEAD
PASS: broad-root request returned HTTP 200
PASS: broad-root variant created the expected file
Result: not affected |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
go.mod:14
- go.mod introduces a temporary replace to a personal fork (github.com/thaJeztah/go-archive) with a "FIXME" note. This makes the dependency provenance and long-term reproducibility unclear for a vendored dependency; for an upstream merge it should be updated to a released/official github.com/moby/go-archive version (or otherwise clearly justified) and the temporary replace removed before landing.
// FIXME(thaJeztah): testing https://github.com/moby/go-archive/pull/93
replace github.com/moby/go-archive => github.com/thaJeztah/go-archive v0.0.0-20260731141554-6c3be27c2c56
go.mod:71
- The PR title says go-archive v0.3.2, but go.mod currently requires github.com/moby/go-archive v0.3.1 (and vendor/modules.txt also reflects v0.3.1). Please align the declared version with the PR intent (either bump to the intended version or update the PR metadata accordingly) so it’s clear what’s being shipped.
github.com/moby/docker-image-spec v1.3.1
github.com/moby/go-archive v0.3.1
github.com/moby/ipvs v1.1.0
GordonTheTurtle
left a comment
There was a problem hiding this comment.
Assessment: 🔴 CRITICAL
This PR is intentionally a WIP / pre-release testing PR (stacked on #53260, awaiting the upstream moby/go-archive#93 release). The vendor changes themselves look correct and security-conscious — the new resolveArchivePath / resolveHardlinkTarget logic is a sound improvement. However two items must be resolved before merge.
Inline findings (2):
| # | Severity | Finding |
|---|---|---|
| 1 | 🔴 High | FIXME replace directive routes moby/go-archive to a personal fork |
| 2 | 🟡 Medium | require pins v0.3.1 but PR title advertises v0.3.2 |
Dismissed (1):
| # | Reason |
|---|---|
| go.sum missing moby/go-archive v0.3.1 hash | Expected/correct: Go only checksums the replacement module when a replace directive is active |
Lower-confidence observations (informational, not blocking):
vendor/modules.txt: the=>replace annotation is ago mod vendorartifact; will self-correct when the replace directive is removedresolveArchivePathhas a documented TOCTOU window between Stat and the subsequent Root operation — bounded byos.Rootcontainment (no escape possible), and explicitly acknowledged in the comment; the trade-off is acceptablecreateImpliedDirectories: the refactor drops thehdr.Typeflag != tar.TypeDirguard, making the function also run for TypeDir entries (idempotent, harmless, but undocumented)
Merge blocker: remove the replace directive and align module version once go-archive#93 ships.
|
Windows failure is unrelated; |
full diff: moby/go-archive@v0.3.1...v0.3.2 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
7caf41c to
eeea07c
Compare
GordonTheTurtle
left a comment
There was a problem hiding this comment.
Assessment: 🟡 NEEDS ATTENTION
This PR vendors github.com/moby/go-archive v0.3.2 to fix a CopyToContainer regression where Go 1.25's stricter os.Root incorrectly rejected paths traversing absolute symlinks (e.g. /var/run → /run). The fix is architecturally sound: os.Root containment is preserved throughout, the relativeEscapeBeforeAbsolute guard prevents relative-escape/absolute-link gadget chains, and the unpackedPaths key consistency fix in diff.go is correct.
Two security-adjacent observations worth noting (see inline comments):
- Acknowledged TOCTOU —
resolveArchivePathresolves the path then hands a string toos.Root; a concurrent symlink swap inside the container can redirect writes intra-root, but cannot escape root (os.Rootcontainment holds). Explicitly noted in the code as a known limitation. isPathEscapesstring-sentinel — the security gate relies on matching Go's unexported error string"path escapes from parent". If the string changes in a future Go release the failure mode is a functionality regression (ENOENT returned instead of resolving), not a security bypass — the firstswitchcase already returns the error before the fallback is reached.
Lower-confidence notes
relativeEscapeBeforeAbsolutescope (rootpath.go): The flag is only tracked before any absolute link is followed. Post-absolute relative symlinks are not monitored by this flag, but they are still bounded byfsRootPath's chroot-style resolution (the OS-level backstop). Low severity, design-intentional.- AUFS metadata ordering (
diff.go): MovingcreateImpliedDirectoriesafter the AUFS-metadata skip is a net correctness improvement; AUFS entries that were immediatelycontinued no longer spuriously trigger parent-directory creation.
| )) | ||
| } | ||
|
|
||
| return filepath.Join(relParent, base), nil |
There was a problem hiding this comment.
[MEDIUM] Acknowledged TOCTOU race: intra-root path misdirection possible during concurrent container activity
resolveArchivePath calls resolveFSRootPath to follow absolute symlinks (e.g. /var/run → /run), producing a resolved native path string. That string is then handed to an os.Root operation (root.Mkdir, root.OpenFile, root.Link, etc.). A process running concurrently inside the container can swap an absolute symlink's target between these two steps, redirecting the extraction write to a different location within the container root.
The code comment explicitly acknowledges this as a "resolution/use race" and a "compatibility workaround" — the subsequent os.Root call still enforces root containment at the OS level, so no path can escape outside the container root. The worst-case impact is intra-root confusion (e.g. a file lands in /run/foo instead of /var/run/foo after a symlink retarget race), not a container escape.
This is tracked upstream as a known limitation pending handle-relative resolution with resolve-in-root semantics (TODO in the comment). No action required on this PR, but reviewers should be aware of the scope of the fix.
| Confidence | Score |
|---|---|
| 🟢 strong | 100/100 |
There was a problem hiding this comment.
Yes, that's a known limitation currently, but does not allow escaping the container rootFs.
| return "", err | ||
| } | ||
|
|
||
| if isPathEscapes(statErr) && (!resolved.followedAbsoluteLink || resolved.relativeEscapeBeforeAbsolute) { |
There was a problem hiding this comment.
[LOW] isPathEscapes relies on a hard-coded Go internal error string that could silently degrade on a Go update
The function matches os.Root's path-containment error by unwrapping to the leaf and comparing err.Error() == "path escapes from parent". This is necessary because Go does not yet export errPathEscapes as a public sentinel (tracked at go.dev/issue/74640).
If Go changes this error message or wrapping structure, isPathEscapes silently returns false for path-escape errors. The failure mode is a functionality regression, not a security bypass: the switch in resolveArchivePath evaluates !os.IsNotExist(statErr) && !isPathEscapes(statErr) — if isPathEscapes incorrectly returns false, a genuine path-escape error would be returned immediately at that case branch, preventing the resolveFSRootPath fallback entirely. Legitimate absolute-symlink paths would break again, but no relative-escape path could silently proceed.
Worth adding a test that verifies isPathEscapes correctly identifies os.Root.Stat's error on the current Go toolchain, to catch breakage at CI time rather than at runtime in production.
| Confidence | Score |
|---|---|
| 🟡 moderate | 60/100 |
There was a problem hiding this comment.
Yup, already upvoted that ticket, but maybe you can find AI friends to also give it a thumbs-up 😉 😂
Revert https://gitea.com/gitea/runner/pulls/1130. Docker 29.7.1 fixed both regressions it worked around, moby/moby#53261 and moby/moby#53260, so only 29.7.0 still needs it. Verified live with a relative and an absolute `/var/run` symlink: without the workaround the copy passes on 29.4.0, 29.6.2 and 29.7.1, and fails on 29.7.0 alone. Fixes: https://gitea.com/gitea/runner/issues/1131 Reviewed-on: https://gitea.com/gitea/runner/pulls/1155 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
CopyToContainerregression in 29.7.0: "path escapes from parent" when copying into paths that traverse/var/run → /runsymlink #53258Summary
Release notes (optional)
A picture of a cute animal (not mandatory but encouraged)