Skip to content

Commit 2f6c984

Browse files
Potential fix for code scanning alert no. 6: Arbitrary file write extracting an archive containing symbolic links
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 63ba972 commit 2f6c984

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

internal/zipx/zipx.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import (
1313
"strings"
1414
)
1515

16+
// isPathWithinBase checks if absPath (absolute, resolved) is under baseAbs (absolute, resolved)
17+
func isPathWithinBase(baseAbs, absPath string) bool {
18+
rel, err := filepath.Rel(baseAbs, absPath)
19+
if err != nil {
20+
return false
21+
}
22+
relClean := filepath.Clean(rel)
23+
return relClean != ".." && !strings.HasPrefix(relClean, ".."+string(filepath.Separator))
24+
}
25+
1626
// Policy defines extraction limits and behavior.
1727
type Policy struct {
1828
MaxFiles int // maximum number of files allowed
@@ -181,6 +191,28 @@ func Unzip(srcZip, destDir string, p Policy) (err error) {
181191
// Normalize a bit (keep relative)
182192
// If symlink target escapes on resolution at runtime, parent check above still blocks via EvalSymlinks
183193
_ = os.Remove(targetAbs) // best-effort replace
194+
195+
// -- Fix: Check resolved destination and symlink target before creating symlink --
196+
// 1. Resolve parent directory's symlinks (already extracted so far).
197+
parentResolved, err := filepath.EvalSymlinks(filepath.Dir(targetAbs))
198+
if err != nil {
199+
if !os.IsNotExist(err) {
200+
return fmt.Errorf("symlink parent resolve error: %w", err)
201+
}
202+
// If parent doesn't exist, mkdirall above does it, so we fallback to intendeed parent
203+
parentResolved = filepath.Dir(targetAbs)
204+
}
205+
linkAbs := filepath.Join(parentResolved, filepath.Base(targetAbs))
206+
if !isPathWithinBase(destAbs, linkAbs) {
207+
return fmt.Errorf("symlink destination escapes extraction root: %q", linkAbs)
208+
}
209+
// 2. Where would the symlink, if created, point to? (Relative to resolved parent.)
210+
targetCandidate := filepath.Join(parentResolved, linkTarget)
211+
// We can't EvalSymlinks on the new symlink yet, but check that the _synthetic resolution_ is within destAbs.
212+
if !isPathWithinBase(destAbs, targetCandidate) {
213+
return fmt.Errorf("symlink target escapes extraction root: %q -> %q", f.Name, linkTarget)
214+
}
215+
184216
if err := os.Symlink(linkTarget, targetAbs); err != nil {
185217
return fmt.Errorf("create symlink: %w", err)
186218
}

0 commit comments

Comments
 (0)