Files
cli/vendor/github.com/moby/go-archive/time_windows.go
Sebastiaan van Stijn f6d6bede46 vendor: github.com/moby/go-archive v0.3.0
full diff: https://github.com/moby/go-archive/compare/v0.2.1...v0.3.0

v0.3.0

This release fixes CVE-2026-17106 / GHSA-hfg8-hc9c-6c3h, where a crafted
tar archive could use links to cause extraction operations to create or
overwrite files outside the intended destination directory.

The issue affected Unpack, UnpackLayer, Untar, UntarUncompressed, and the
ApplyLayer helpers. Users should upgrade and avoid extracting untrusted
archives with earlier versions.

What's Changed

* archive: harden tar extraction against path traversal
* archive: do not follow reparse points in chtimes
* archive: fix creation time updates on Windows
* archive: minor cleanups and godoc touch-up
* archive: RebaseArchiveEntries: fix archive path rebasing

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-07-30 19:17:27 +02:00

112 lines
3.7 KiB
Go

package archive
import (
"errors"
"os"
"path/filepath"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
// chtimes changes the access and modification time of a file at the given
// path relative to root.
//
// Symlink entries are handled separately through lchtimes. The final path
// component is expected not to be a reparse point; if one is encountered,
// chtimes returns an error.
//
// Callers must use boundTime to ensure timestamps are within the range
// supported by os.Chtimes.
func chtimes(root *os.Root, name string, atime, mtime time.Time) error {
parent, err := root.OpenFile(filepath.Dir(name), os.O_RDONLY, 0)
if err != nil {
return err
}
defer parent.Close()
// Symlink entries are handled by lchtimes. The destination for all
// chtimes callers is therefore expected not to be a reparse point.
//
// Do not follow the final component: if it was concurrently replaced
// with a reparse point, fail instead of updating its target.
return chtimesAt(parent, filepath.Base(name), atime, mtime, true)
}
func lchtimes(root *os.Root, name string, atime time.Time, mtime time.Time) error {
return nil
}
func chtimesAt(parent *os.File, name string, atime, mtime time.Time, noFollow bool) error {
h, err := openForWriteAttributesAt(windows.Handle(parent.Fd()), name, noFollow)
if err != nil {
if noFollow && errors.Is(err, windows.STATUS_REPARSE_POINT_ENCOUNTERED) {
// Encountering a reparse point when noFollow is requested is unexpected.
// Treat it as a potential breakout to fail extraction safely.
return breakoutError(err)
}
return err
}
defer func() { _ = windows.Close(h) }()
var (
creationTime = windows.NsecToFiletime(mtime.UnixNano())
accessTime = windows.NsecToFiletime(atime.UnixNano())
modificationTime = windows.NsecToFiletime(mtime.UnixNano())
)
return windows.SetFileTime(h, &creationTime, &accessTime, &modificationTime)
}
// openForWriteAttributesAt opens name relative to parent with permission to
// modify its file attributes. If noFollow is true, it does not follow reparse
// points.
//
// This implementation is based on Go's internal Windows Openat support:
//
// https://github.com/golang/go/blob/go1.26.0/src/internal/syscall/windows/at_windows.go
//
// It is used by os.Root's Windows implementation for root-relative filesystem
// operations:
//
// https://github.com/golang/go/blob/go1.26.0/src/os/root_windows.go
//
// Keep this implementation aligned with the upstream code until an equivalent
// operation is available from golang.org/x/sys/windows.
func openForWriteAttributesAt(parent windows.Handle, name string, noFollow bool) (windows.Handle, error) {
name16, err := windows.UTF16FromString(name)
if err != nil {
return windows.InvalidHandle, err
}
attrs := uint32(windows.OBJ_CASE_INSENSITIVE)
if noFollow {
attrs |= windows.OBJ_DONT_REPARSE
}
var handle windows.Handle
err = windows.NtCreateFile(
&handle,
windows.SYNCHRONIZE|windows.FILE_WRITE_ATTRIBUTES,
&windows.OBJECT_ATTRIBUTES{
Length: uint32(unsafe.Sizeof(windows.OBJECT_ATTRIBUTES{})),
RootDirectory: parent,
ObjectName: &windows.NTUnicodeString{
Length: uint16((len(name16) - 1) * 2), // #nosec G115 -- Length is USHORT by definition. A Windows path component cannot exceed uint16 bytes.
MaximumLength: uint16(len(name16) * 2), // #nosec G115 -- MaximumLength is USHORT by definition. A Windows path component cannot exceed uint16 bytes.
Buffer: &name16[0],
},
Attributes: attrs,
},
&windows.IO_STATUS_BLOCK{},
nil,
windows.FILE_ATTRIBUTE_NORMAL,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
windows.FILE_OPEN,
windows.FILE_OPEN_FOR_BACKUP_INTENT|windows.FILE_SYNCHRONOUS_IO_NONALERT,
0, // EA buffer
0, // EA length
)
return handle, err
}