mirror of
https://github.com/moby/moby.git
synced 2026-08-04 23:21:00 +00:00
Merge pull request #53098 from thaJeztah/bump_go_archive
vendor: github.com/moby/go-archive main / v0.3.0-dev
This commit is contained in:
2
go.mod
2
go.mod
@@ -64,7 +64,7 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/moby/buildkit v0.32.0-rc2
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/moby/go-archive v0.2.1
|
||||
github.com/moby/go-archive v0.2.2-0.20260724112411-2ff9bfb8b2ee
|
||||
github.com/moby/ipvs v1.1.0
|
||||
github.com/moby/locker v1.0.1
|
||||
github.com/moby/moby/api v1.55.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -547,8 +547,8 @@ github.com/moby/buildkit v0.32.0-rc2 h1:2XiJmnPSZyJ0akoKG2ShWAFbWRT5DKvyAI2+Johq
|
||||
github.com/moby/buildkit v0.32.0-rc2/go.mod h1:Y10FBWvqxl/Wmhdzjee1Y2wQfjifTiwxENIUdaVNdME=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.2.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc=
|
||||
github.com/moby/go-archive v0.2.1/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||
github.com/moby/go-archive v0.2.2-0.20260724112411-2ff9bfb8b2ee h1:VUrUP/hu1E43KunXVZlHsNstFGeZOpm/CQoLx5OSuMw=
|
||||
github.com/moby/go-archive v0.2.2-0.20260724112411-2ff9bfb8b2ee/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||
github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ=
|
||||
github.com/moby/ipvs v1.1.0/go.mod h1:4VJMWuf098bsUMmZEiD4Tjk/O7mOn3l1PTD3s4OoYAs=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
|
||||
292
vendor/github.com/moby/go-archive/archive.go
generated
vendored
292
vendor/github.com/moby/go-archive/archive.go
generated
vendored
@@ -8,9 +8,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -95,6 +97,23 @@ func NewDefaultArchiver() *Archiver {
|
||||
return &Archiver{Untar: Untar}
|
||||
}
|
||||
|
||||
// isPathEscapes reports whether err is os.Root's path-containment error.
|
||||
//
|
||||
// os.Root currently returns an unexported errPathEscapes sentinel, so callers
|
||||
// cannot detect it with errors.Is. Keep the string comparison isolated here
|
||||
// until Go exports the error; see https://go.dev/issue/74640.
|
||||
func isPathEscapes(err error) bool {
|
||||
// https://github.com/golang/go/blob/go1.26.5/src/os/file.go#L421
|
||||
const errPathEscapes = "path escapes from parent"
|
||||
for err != nil {
|
||||
if errors.Unwrap(err) == nil {
|
||||
return err.Error() == errPathEscapes
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// breakoutErr marks errors caused by archive breakout attempts.
|
||||
// Unit tests use it to distinguish expected breakout failures from other
|
||||
// errors.
|
||||
@@ -268,7 +287,7 @@ func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error {
|
||||
|
||||
type tarWhiteoutConverter interface {
|
||||
ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error)
|
||||
ConvertRead(*tar.Header, string) (bool, error)
|
||||
ConvertRead(*os.Root, *tar.Header, string) (bool, error)
|
||||
}
|
||||
|
||||
type tarAppender struct {
|
||||
@@ -357,7 +376,7 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error {
|
||||
// handle re-mapping container ID mappings back to host ID mappings before
|
||||
// writing tar headers/files. We skip whiteout files because they were written
|
||||
// by the kernel and already have proper ownership relative to the host
|
||||
if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() {
|
||||
if !isOverlayWhiteout && !strings.HasPrefix(path.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() {
|
||||
uid, gid, err := getFileUIDGID(fi.Sys())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -418,7 +437,10 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
|
||||
// createTarFile extracts a single tar entry into the given root. dstPath is the
|
||||
// root-relative path of the entry being extracted, in native (host-separator)
|
||||
// form so it can be passed directly to os.Root methods and fsRootPath.
|
||||
func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
|
||||
var (
|
||||
Lchown = true
|
||||
inUserns, bestEffortXattrs bool
|
||||
@@ -440,18 +462,24 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
// Create directory unless it exists as a directory already.
|
||||
// In that case we just want to merge the two
|
||||
if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() {
|
||||
if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil {
|
||||
// Create directory unless it already exists as one; merge in that case.
|
||||
// os.Root.Mkdir only accepts the nine least-significant permission
|
||||
// bits; special bits (setuid, setgid, sticky) are applied afterward
|
||||
// by handleLChmod via root.Chmod.
|
||||
if fi, err := root.Lstat(dstPath); err != nil || !fi.IsDir() {
|
||||
if err := root.Mkdir(dstPath, hdrInfo.Mode()&0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case tar.TypeReg:
|
||||
// Source is regular file. We use sequential file access to avoid depleting
|
||||
// the standby list on Windows. On Linux, this equates to a regular os.OpenFile.
|
||||
file, err := sequential.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
|
||||
// Source is a regular file. Use os.Root.OpenFile so that all
|
||||
// path resolution is bounded within root using openat(2) semantics.
|
||||
// os.Root.OpenFile only accepts the nine least-significant permission
|
||||
// bits; special bits are applied afterward by handleLChmod.
|
||||
// We use sequential file access to avoid depleting the standby list
|
||||
// on Windows (go1.26). On Linux, this equates to a regular os.OpenFile.
|
||||
file, err := root.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|windows_O_FILE_FLAG_SEQUENTIAL_SCAN, hdrInfo.Mode()&0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -466,14 +494,12 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
log.G(context.TODO()).WithFields(log.Fields{"path": dstPath, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns")
|
||||
return nil
|
||||
}
|
||||
// Handle this is an OS-specific way
|
||||
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
|
||||
if err := handleTarTypeBlockCharFifo(root, hdr, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeFifo:
|
||||
// Handle this is an OS-specific way
|
||||
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
|
||||
if err := handleTarTypeBlockCharFifo(root, hdr, dstPath); err != nil {
|
||||
if inUserns && errors.Is(err, syscall.EPERM) {
|
||||
// In most cases, cannot create a fifo if running in user namespace
|
||||
log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": dstPath, "type": hdr.Typeflag}).Debug("creating fifo node in a userns")
|
||||
@@ -483,27 +509,29 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
}
|
||||
|
||||
case tar.TypeLink:
|
||||
// #nosec G305 -- The target path is checked for path traversal.
|
||||
linkTarget := filepath.Join(extractDir, hdr.Linkname)
|
||||
// check for hardlink breakout
|
||||
if !strings.HasPrefix(linkTarget, extractDir) {
|
||||
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", linkTarget, hdr.Linkname))
|
||||
// Defence in depth: root.Link's containment is limited when
|
||||
// dest is a volume root.
|
||||
linkname := path.Clean(hdr.Linkname)
|
||||
if linkname == "." || !filepath.IsLocal(linkname) {
|
||||
return breakoutError(fmt.Errorf("invalid hardlink target %q", hdr.Linkname))
|
||||
}
|
||||
if err := os.Link(linkTarget, dstPath); err != nil {
|
||||
if err := root.Link(filepath.FromSlash(linkname), dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeSymlink:
|
||||
// path -> hdr.Linkname = targetPath
|
||||
// e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file
|
||||
targetPath := filepath.Join(filepath.Dir(dstPath), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal.
|
||||
// Symlink targets are archive data, not filesystem paths. Preserve the
|
||||
// target verbatim rather than cleaning or converting it (filepath.FromSlash).
|
||||
linkTarget := hdr.Linkname
|
||||
|
||||
// the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because
|
||||
// that symlink would first have to be created, which would be caught earlier, at this very check:
|
||||
if !strings.HasPrefix(targetPath, extractDir) {
|
||||
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", dstPath, hdr.Linkname))
|
||||
}
|
||||
if err := os.Symlink(hdr.Linkname, dstPath); err != nil {
|
||||
// os.Root.Symlink contains the symlink's location (newname) within
|
||||
// root but stores the target (oldname) verbatim, so absolute targets
|
||||
// such as /usr/lib -- common and legitimate in container images -- are
|
||||
// preserved rather than rejected. The symlink node is therefore always
|
||||
// created within root via openat(2) semantics, without resolving to an
|
||||
// absolute path; containment applies when the symlink is followed, not
|
||||
// at creation.
|
||||
if err := root.Symlink(linkTarget, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -520,7 +548,7 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
if chownOpts == nil {
|
||||
chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid}
|
||||
}
|
||||
if err := os.Lchown(dstPath, chownOpts.UID, chownOpts.GID); err != nil {
|
||||
if err := root.Lchown(dstPath, chownOpts.UID, chownOpts.GID); err != nil {
|
||||
var msg string
|
||||
if inUserns && errors.Is(err, syscall.EINVAL) {
|
||||
msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)"
|
||||
@@ -530,12 +558,21 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
}
|
||||
|
||||
var xattrErrs []string
|
||||
absPath := sync.OnceValues(func() (string, error) {
|
||||
return fsRootPath(root.Name(), dstPath)
|
||||
})
|
||||
for key, value := range hdr.PAXRecords {
|
||||
xattr, ok := strings.CutPrefix(key, paxSchilyXattr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err := lsetxattr(dstPath, xattr, []byte(value), 0); err != nil {
|
||||
// os.Root has no xattr support; use the absolute path derived from
|
||||
// the root so the path remains bounded.
|
||||
ap, err := absPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := lsetxattr(ap, xattr, []byte(value), 0); err != nil {
|
||||
if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) {
|
||||
// EPERM occurs if modifying xattrs is not allowed. This can
|
||||
// happen when running in userns with restrictions (ChromeOS).
|
||||
@@ -554,7 +591,7 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
|
||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||
// must happen after chown, as that can modify the file mode
|
||||
if err := handleLChmod(hdr, dstPath, hdrInfo); err != nil {
|
||||
if err := handleLChmod(root, dstPath, hdr, hdrInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -564,20 +601,20 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeSymlink:
|
||||
// Apply timestamps to the symlink itself (AT_SYMLINK_NOFOLLOW).
|
||||
if err := lchtimes(dstPath, aTime, mTime); err != nil {
|
||||
if err := lchtimes(root, dstPath, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeLink:
|
||||
// Follow the hardlink only when its target is not itself a symlink.
|
||||
fi, err := os.Lstat(hdr.Linkname)
|
||||
fi, err := root.Lstat(filepath.FromSlash(path.Clean(hdr.Linkname)))
|
||||
if err == nil && fi.Mode()&os.ModeSymlink == 0 {
|
||||
if err := chtimes(dstPath, aTime, mTime); err != nil {
|
||||
if err := root.Chtimes(dstPath, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
// All other file types follow symlinks.
|
||||
if err := chtimes(dstPath, aTime, mTime); err != nil {
|
||||
if err := root.Chtimes(dstPath, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -823,14 +860,28 @@ func (t *Tarballer) Do() {
|
||||
}
|
||||
}
|
||||
|
||||
// unpackedDir records a directory whose mtime must be restored after all
|
||||
// entries are extracted, along with the root-relative entry name used during
|
||||
// extraction.
|
||||
type unpackedDir struct {
|
||||
hdr *tar.Header
|
||||
name string // root-relative entry name
|
||||
}
|
||||
|
||||
// Unpack unpacks the decompressedArchive to dest with options.
|
||||
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error {
|
||||
if options == nil {
|
||||
options = &TarOptions{}
|
||||
}
|
||||
root, err := os.OpenRoot(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = root.Close() }()
|
||||
|
||||
tr := tar.NewReader(decompressedArchive)
|
||||
|
||||
var dirs []*tar.Header
|
||||
var dirs []unpackedDir
|
||||
whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
@@ -851,48 +902,50 @@ loop:
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
// This keeps "../" as-is, but normalizes "/../" to "/". Or Windows:
|
||||
// This keeps "..\" as-is, but normalizes "\..\" to "\".
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
// Strip a leading "/" so absolute entries stay root-relative, and
|
||||
// normalize the POSIX tar path. Skip entries referring to the extraction
|
||||
// root and reject paths that escape it.
|
||||
name := path.Clean(strings.TrimLeft(hdr.Name, "/"))
|
||||
if name == "." {
|
||||
continue
|
||||
}
|
||||
if !filepath.IsLocal(name) {
|
||||
return breakoutError(fmt.Errorf("invalid entry name %q", hdr.Name))
|
||||
}
|
||||
for _, exclude := range options.ExcludePatterns {
|
||||
if strings.HasPrefix(hdr.Name, exclude) {
|
||||
if strings.HasPrefix(name, exclude) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the parent directory exists.
|
||||
err = createImpliedDirectories(dest, hdr, options)
|
||||
if err != nil {
|
||||
return err
|
||||
hdr.Name = name
|
||||
|
||||
// Skip entries whose name (or hardlink target) Windows cannot represent.
|
||||
if err := unrepresentableOnWindows(hdr); err != nil {
|
||||
log.G(context.TODO()).Warnf("Windows: ignoring entry: %v", err)
|
||||
continue loop
|
||||
}
|
||||
|
||||
// #nosec G305 -- The joined path is checked for path traversal.
|
||||
dstPath := filepath.Join(dest, hdr.Name)
|
||||
rel, err := filepath.Rel(dest, dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||
}
|
||||
// dstPath is the native (host-separator) form of the entry name,
|
||||
// used at all filesystem boundaries (os.Root methods, fsRootPath).
|
||||
// hdr.Name stays POSIX (forward-slash) for logical string checks.
|
||||
dstPath := filepath.FromSlash(hdr.Name)
|
||||
|
||||
// If dstPath exists we almost always just want to remove and replace it.
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(dstPath); err == nil {
|
||||
if fi, err := root.Lstat(dstPath); err == nil {
|
||||
if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir {
|
||||
// If NoOverwriteDirNonDir is true then we cannot replace
|
||||
// an existing directory with a non-directory from the archive.
|
||||
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", dstPath, dest)
|
||||
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", hdr.Name, dest)
|
||||
}
|
||||
|
||||
if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir {
|
||||
// If NoOverwriteDirNonDir is true then we cannot replace
|
||||
// an existing non-directory with a directory from the archive.
|
||||
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", dstPath, dest)
|
||||
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", hdr.Name, dest)
|
||||
}
|
||||
|
||||
if fi.IsDir() && hdr.Name == "." {
|
||||
@@ -900,7 +953,7 @@ loop:
|
||||
}
|
||||
|
||||
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
||||
if err := os.RemoveAll(dstPath); err != nil {
|
||||
if err := root.RemoveAll(dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -910,8 +963,16 @@ loop:
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that the parent directory exists.
|
||||
//
|
||||
// This must be done before whiteoutConverter.ConvertRead, which
|
||||
// may set xattrs on the directory or create whiteout files.
|
||||
if err := createImpliedDirectories(root, hdr, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if whiteoutConverter != nil {
|
||||
writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath)
|
||||
writeFile, err := whiteoutConverter.ConvertRead(root, hdr, dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -920,49 +981,122 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil {
|
||||
if err := createTarFile(root, dstPath, hdr, tr, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
dirs = append(dirs, unpackedDir{hdr: hdr, name: dstPath})
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
||||
dstPath := filepath.Join(dest, hdr.Name)
|
||||
if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
|
||||
for _, d := range dirs {
|
||||
aTime := boundTime(latestTime(d.hdr.AccessTime, d.hdr.ModTime))
|
||||
if err := root.Chtimes(d.name, aTime, boundTime(d.hdr.ModTime)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unrepresentableOnWindows returns an error describing why a tar entry cannot
|
||||
// be faithfully created on Windows, or nil if it can (always on non-Windows).
|
||||
// On Windows ":" is illegal in a filename and "\" is a path separator, so a tar
|
||||
// name or hardlink target containing them (they use POSIX semantics) would be
|
||||
// misinterpreted by os.Root (e.g. "a\b" resolved as two components). Symlink
|
||||
// targets are stored verbatim (not resolved at creation), so they are exempt.
|
||||
func unrepresentableOnWindows(hdr *tar.Header) error {
|
||||
if runtime.GOOS != "windows" {
|
||||
return nil
|
||||
}
|
||||
if strings.ContainsAny(hdr.Name, `:\`) {
|
||||
return fmt.Errorf("entry name %q contains a character Windows cannot represent in a path", hdr.Name)
|
||||
}
|
||||
// A hardlink target is resolved within the root by os.Root.Link; a symlink
|
||||
// target is stored verbatim, so only hardlinks need the target checked.
|
||||
if hdr.Typeflag == tar.TypeLink && strings.ContainsAny(hdr.Linkname, `:\`) {
|
||||
return fmt.Errorf("hardlink target %q contains a character Windows cannot represent in a path", hdr.Linkname)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createImpliedDirectories will create all parent directories of the current path with default permissions, if they do
|
||||
// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is
|
||||
// defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus
|
||||
// we most both create them and choose metadata like permissions.
|
||||
func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error {
|
||||
//
|
||||
// The caller must have normalized hdr.Name (no leading ".." components).
|
||||
// All directory creation is performed via root so it is bounded within the
|
||||
// destination at the OS level (openat(2) semantics), preventing escape via
|
||||
// symlinks in the destination tree.
|
||||
func createImpliedDirectories(root *os.Root, hdr *tar.Header, options *TarOptions) error {
|
||||
// For non-directory entries, ensure that the parent directory exists.
|
||||
if hdr.Typeflag != tar.TypeDir {
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
if options.NoLchown {
|
||||
return os.MkdirAll(parentPath, ImpliedDirectoryMode)
|
||||
}
|
||||
// RootPair() is confined inside this loop as most cases will not require a call, so we can spend some
|
||||
// unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche
|
||||
// usage that reduces the portability of an image.
|
||||
uid, gid := options.IDMap.RootPair()
|
||||
parent := filepath.FromSlash(path.Dir(strings.TrimSuffix(hdr.Name, "/")))
|
||||
// Skip when the parent is the root itself; nothing to create.
|
||||
if parent == "." || parent == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := root.Lstat(parent); err == nil {
|
||||
return nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// RootPair() is confined inside this loop as most cases will not require a call, so we can spend some
|
||||
// unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche
|
||||
// usage that reduces the portability of an image.
|
||||
uid, gid := options.IDMap.RootPair()
|
||||
|
||||
err = user.MkdirAllAndChown(parentPath, ImpliedDirectoryMode, uid, gid, user.WithOnlyNew)
|
||||
// Similar to [user.MkdirAllAndChown]
|
||||
//
|
||||
// [user.MkdirAllAndChown]: https://pkg.go.dev/github.com/moby/sys/user#MkdirAllAndChown
|
||||
var cur string
|
||||
for c := range strings.SplitSeq(parent, string(os.PathSeparator)) {
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
cur = filepath.Join(cur, c)
|
||||
if err := root.Mkdir(cur, ImpliedDirectoryMode); err != nil {
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
fi, err := root.Stat(cur)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
return &os.PathError{Op: "mkdir", Path: cur, Err: syscall.ENOTDIR}
|
||||
}
|
||||
if options.NoLchown {
|
||||
continue
|
||||
}
|
||||
// Only the successful Mkdir case is newly-created.
|
||||
dir, err := root.Open(cur)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uid != 0 || gid != 0 {
|
||||
if err := dir.Chown(uid, gid); err != nil {
|
||||
_ = dir.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
// root.Mkdir applies the mode subject to the process umask, so
|
||||
// re-apply it with Chmod to guarantee ImpliedDirectoryMode
|
||||
// independent of umask, matching the previous MkdirAllAndChown
|
||||
// behavior.
|
||||
if err := dir.Chmod(ImpliedDirectoryMode); err != nil {
|
||||
_ = dir.Close()
|
||||
return err
|
||||
}
|
||||
if err := dir.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
vendor/github.com/moby/go-archive/archive_linux.go
generated
vendored
27
vendor/github.com/moby/go-archive/archive_linux.go
generated
vendored
@@ -76,7 +76,7 @@ func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) {
|
||||
func (c overlayWhiteoutConverter) ConvertRead(root *os.Root, hdr *tar.Header, filePath string) (bool, error) {
|
||||
base := filepath.Base(filePath)
|
||||
dir := filepath.Dir(filePath)
|
||||
|
||||
@@ -85,9 +85,15 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string)
|
||||
return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name)
|
||||
|
||||
case WhiteoutOpaqueDir:
|
||||
parent, err := root.Open(dir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
// If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay.
|
||||
if err := unix.Setxattr(dir, c.opaqueXattr, []byte{'y'}, 0); err != nil {
|
||||
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, c.opaqueXattr, err)
|
||||
if err := unix.Fsetxattr(int(parent.Fd()), c.opaqueXattr, []byte{'y'}, 0); err != nil {
|
||||
return false, fmt.Errorf("fsetxattr('%s', %s=y): %w", dir, c.opaqueXattr, err)
|
||||
}
|
||||
// Don't write the whiteout file itself.
|
||||
return false, nil
|
||||
@@ -98,9 +104,16 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string)
|
||||
// Regular file.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
parent, err := root.Open(dir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
// If a file was deleted, and we are using overlay, we need to create a character device.
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
|
||||
if err := unix.Mknodat(int(parent.Fd()), originalBase, unix.S_IFCHR, 0); err != nil {
|
||||
return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err)
|
||||
}
|
||||
|
||||
@@ -117,9 +130,9 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string)
|
||||
// OverlayFS documents whiteouts in terms of a character device with device
|
||||
// number 0:0, not ownership: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
|
||||
//
|
||||
// If ownership is not required, this Lchown can be removed to avoid the remaining TOCTOU window.
|
||||
if err := os.Lchown(originalPath, hdr.Uid, hdr.Gid); err != nil {
|
||||
return false, err
|
||||
// If ownership is not required, this Fchownat can be removed to avoid the remaining TOCTOU window.
|
||||
if err := unix.Fchownat(int(parent.Fd()), originalBase, hdr.Uid, hdr.Gid, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return false, &os.PathError{Op: "lchown", Path: originalPath, Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
vendor/github.com/moby/go-archive/archive_unix.go
generated
vendored
85
vendor/github.com/moby/go-archive/archive_unix.go
generated
vendored
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -59,7 +60,7 @@ func getFileUIDGID(stat any) (int, int, error) {
|
||||
//
|
||||
// Creating device nodes is not supported when running in a user namespace,
|
||||
// produces a [syscall.EPERM] in most cases.
|
||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error {
|
||||
func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, dstPath string) error {
|
||||
mode := uint32(hdr.Mode & 0o7777)
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeBlock:
|
||||
@@ -80,20 +81,80 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error {
|
||||
return fmt.Errorf("device number %d:%d for %q out of range: %w", hdr.Devmajor, hdr.Devminor, hdr.Name, errInvalidArchive)
|
||||
}
|
||||
|
||||
return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
|
||||
// Prefer mknodat; fall back to a bounded path where unavailable.
|
||||
return mknodInRoot(root, dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
|
||||
}
|
||||
|
||||
func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error {
|
||||
if hdr.Typeflag == tar.TypeLink {
|
||||
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
||||
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
// handleLChmod applies the mode from hdrInfo to dstPath within root, skipping
|
||||
// symlinks (there is no lchmod). For hardlinks, the mode is applied only when
|
||||
// the link target is itself not a symlink.
|
||||
func handleLChmod(root *os.Root, dstPath string, hdr *tar.Header, hdrInfo os.FileInfo) error {
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeSymlink:
|
||||
return nil
|
||||
|
||||
case tar.TypeLink:
|
||||
// If the target is a symlink, there is no way to chmod the hardlink
|
||||
// without following it.
|
||||
fi, err := root.Lstat(filepath.FromSlash(path.Clean(hdr.Linkname)))
|
||||
if err != nil || fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
return chmodNoSymlink(root, dstPath, hdrInfo.Mode())
|
||||
|
||||
default:
|
||||
return chmodNoSymlink(root, dstPath, hdrInfo.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
// chmodNoSymlink applies mode to a non-symlink entry.
|
||||
//
|
||||
// Callers must have already excluded symlink entries.
|
||||
func chmodNoSymlink(root *os.Root, name string, mode os.FileMode) error {
|
||||
parent, err := root.OpenFile(filepath.Dir(name), os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
base := filepath.Base(name)
|
||||
perm := fileModeToPerm(mode)
|
||||
// #nosec G115 -- ignore integer overflow conversion for parent.Fd
|
||||
if err := unix.Fchmodat(int(parent.Fd()), base, perm, unix.AT_SYMLINK_NOFOLLOW); err == nil {
|
||||
return nil
|
||||
} else if !errors.Is(err, syscall.EOPNOTSUPP) {
|
||||
return &os.PathError{Op: "fchmodat2", Path: name, Err: err}
|
||||
}
|
||||
|
||||
// Fallback for systems that cannot perform fchmodat with AT_SYMLINK_NOFOLLOW.
|
||||
// Open the entry without following symlinks and apply the mode through the
|
||||
// resulting file descriptor.
|
||||
// #nosec G115 -- ignore integer overflow conversion for parent.Fd
|
||||
fd, err := unix.Openat(int(parent.Fd()), base, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "openat", Path: name, Err: err}
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
if err := unix.Fchmod(fd, perm); err != nil {
|
||||
return &os.PathError{Op: "fchmod", Path: name, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fileModeToPerm returns the subset of an os.FileMode that can be applied
|
||||
// by chmod.
|
||||
func fileModeToPerm(mode os.FileMode) uint32 {
|
||||
perm := uint32(mode.Perm())
|
||||
|
||||
if mode&os.ModeSetuid != 0 {
|
||||
perm |= unix.S_ISUID
|
||||
}
|
||||
if mode&os.ModeSetgid != 0 {
|
||||
perm |= unix.S_ISGID
|
||||
}
|
||||
if mode&os.ModeSticky != 0 {
|
||||
perm |= unix.S_ISVTX
|
||||
}
|
||||
return perm
|
||||
}
|
||||
|
||||
5
vendor/github.com/moby/go-archive/archive_windows.go
generated
vendored
5
vendor/github.com/moby/go-archive/archive_windows.go
generated
vendored
@@ -48,11 +48,12 @@ func getInodeFromStat(stat any) (uint64, error) {
|
||||
|
||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
||||
func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
||||
// handleLChmod is a no-op on Windows because chmod is not supported.
|
||||
func handleLChmod(root *os.Root, path string, hdr *tar.Header, hdrInfo os.FileInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
21
vendor/github.com/moby/go-archive/dev_darwin.go
generated
vendored
Normal file
21
vendor/github.com/moby/go-archive/dev_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build darwin
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) error {
|
||||
return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
|
||||
}
|
||||
|
||||
func mknodInRoot(root *os.Root, path string, mode uint32, dev uint64) error {
|
||||
abs, err := fsRootPath(root.Name(), path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return unix.Mknod(abs, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
|
||||
}
|
||||
17
vendor/github.com/moby/go-archive/dev_freebsd.go
generated
vendored
17
vendor/github.com/moby/go-archive/dev_freebsd.go
generated
vendored
@@ -2,8 +2,23 @@
|
||||
|
||||
package archive
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) error {
|
||||
return unix.Mknod(path, mode, dev)
|
||||
}
|
||||
|
||||
func mknodInRoot(root *os.Root, path string, mode uint32, dev uint64) error {
|
||||
parent, err := root.OpenFile(filepath.Dir(path), os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
return unix.Mknodat(int(parent.Fd()), filepath.Base(path), mode, dev)
|
||||
}
|
||||
|
||||
19
vendor/github.com/moby/go-archive/dev_unix.go
generated
vendored
19
vendor/github.com/moby/go-archive/dev_unix.go
generated
vendored
@@ -1,9 +1,24 @@
|
||||
//go:build !windows && !freebsd
|
||||
//go:build !darwin && !freebsd && !windows
|
||||
|
||||
package archive
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) error {
|
||||
return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
|
||||
}
|
||||
|
||||
func mknodInRoot(root *os.Root, path string, mode uint32, dev uint64) error {
|
||||
parent, err := root.OpenFile(filepath.Dir(path), os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
return unix.Mknodat(int(parent.Fd()), filepath.Base(path), mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
|
||||
}
|
||||
|
||||
128
vendor/github.com/moby/go-archive/diff.go
generated
vendored
128
vendor/github.com/moby/go-archive/diff.go
generated
vendored
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
@@ -20,9 +20,17 @@ import (
|
||||
// compressed or uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) {
|
||||
root, err := os.OpenRoot(dest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer root.Close()
|
||||
|
||||
tr := tar.NewReader(layer)
|
||||
|
||||
var dirs []*tar.Header
|
||||
var dirs []unpackedDir
|
||||
// unpackedPaths tracks root-relative paths already written in this layer
|
||||
// so that the AUFS opaque-whiteout walk knows which paths to preserve.
|
||||
unpackedPaths := make(map[string]struct{})
|
||||
|
||||
if options == nil {
|
||||
@@ -45,32 +53,26 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
|
||||
size += hdr.Size
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
// Strip a leading "/" so absolute entries stay root-relative, and
|
||||
// normalize the POSIX tar path. Skip entries referring to the extraction
|
||||
// root and reject paths that escape it.
|
||||
name := path.Clean(strings.TrimLeft(hdr.Name, "/"))
|
||||
if name == "." {
|
||||
continue
|
||||
}
|
||||
if !filepath.IsLocal(name) {
|
||||
return 0, breakoutError(fmt.Errorf("invalid entry name %q", hdr.Name))
|
||||
}
|
||||
hdr.Name = name
|
||||
|
||||
// Windows does not support filenames with colons in them. Ignore
|
||||
// these files. This is not a problem though (although it might
|
||||
// appear that it is). Let's suppose a client is running docker pull.
|
||||
// The daemon it points to is Windows. Would it make sense for the
|
||||
// client to be doing a docker pull Ubuntu for example (which has files
|
||||
// with colons in the name under /usr/share/man/man3)? No, absolutely
|
||||
// not as it would really only make sense that they were pulling a
|
||||
// Windows image. However, for development, it is necessary to be able
|
||||
// to pull Linux images which are in the repository.
|
||||
//
|
||||
// TODO Windows. Once the registry is aware of what images are Windows-
|
||||
// specific or Linux-specific, this warning should be changed to an error
|
||||
// to cater for the situation where someone does manage to upload a Linux
|
||||
// image but have it tagged as Windows inadvertently.
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.Contains(hdr.Name, ":") {
|
||||
log.G(context.TODO()).Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
|
||||
continue
|
||||
}
|
||||
// Skip entries whose name (or hardlink target) Windows cannot represent.
|
||||
if err := unrepresentableOnWindows(hdr); err != nil {
|
||||
log.G(context.TODO()).Warnf("Windows: ignoring entry: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure that the parent directory exists.
|
||||
err = createImpliedDirectories(dest, hdr, options)
|
||||
err = createImpliedDirectories(root, hdr, options)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -81,7 +83,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
// We don't want this directory, but we need the files in them so that
|
||||
// such hardlinks can be resolved.
|
||||
if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
|
||||
basename := filepath.Base(hdr.Name)
|
||||
basename := path.Base(hdr.Name)
|
||||
aufsHardlinks[basename] = hdr
|
||||
if aufsTempdir == "" {
|
||||
if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil {
|
||||
@@ -89,47 +91,63 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, options); err != nil {
|
||||
aufsRoot, err := os.OpenRoot(aufsTempdir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cerr := createTarFile(aufsRoot, basename, hdr, tr, options)
|
||||
_ = aufsRoot.Close()
|
||||
if cerr != nil {
|
||||
return 0, cerr
|
||||
}
|
||||
}
|
||||
|
||||
if hdr.Name != WhiteoutOpaqueDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// #nosec G305 -- The joined path is guarded against path traversal.
|
||||
dstPath := filepath.Join(dest, hdr.Name)
|
||||
rel, err := filepath.Rel(dest, dstPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Note as these operations are platform specific, so must the slash be.
|
||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||
}
|
||||
// dstPath is the native (host-separator) form of the entry name,
|
||||
// used at all filesystem boundaries (os.Root methods, fsRootPath).
|
||||
// The tar-header name (hdr.Name) is POSIX, so convert it here.
|
||||
dstPath := filepath.FromSlash(hdr.Name)
|
||||
base := filepath.Base(dstPath)
|
||||
|
||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
||||
dir := filepath.Dir(dstPath)
|
||||
if base == WhiteoutOpaqueDir {
|
||||
_, err := os.Lstat(dir)
|
||||
_, err := root.Lstat(dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error {
|
||||
// Walk the absolute directory so we can call os.RemoveAll on
|
||||
// paths outside the walk callback's reach, then convert each
|
||||
// walked path back to a root-relative name for the
|
||||
// unpackedPaths check.
|
||||
// fsRootPath walks each path component and bounds any symlinks
|
||||
// within the root to prevent TOCTOU symlink attacks.
|
||||
absDir, err := fsRootPath(root.Name(), dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = filepath.WalkDir(absDir, func(p string, info os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // parent was deleted
|
||||
return nil // parent was deleted
|
||||
}
|
||||
return err
|
||||
}
|
||||
if path == dir {
|
||||
if p == absDir {
|
||||
return nil
|
||||
}
|
||||
if _, exists := unpackedPaths[path]; !exists {
|
||||
return os.RemoveAll(path) // #nosec G122 -- FIXME: consider root-scoped APIs (e.g. os.Root) to prevent symlink TOCTOU traversal
|
||||
rel, err := filepath.Rel(root.Name(), p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unpackedPaths is keyed by root-relative slash paths; convert
|
||||
// filepath.WalkDir's native path before looking it up.
|
||||
if _, exists := unpackedPaths[filepath.ToSlash(rel)]; !exists {
|
||||
return root.RemoveAll(rel)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -139,7 +157,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
} else {
|
||||
originalBase := base[len(WhiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
if err := root.RemoveAll(originalPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -148,9 +166,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(dstPath); err == nil {
|
||||
if fi, err := root.Lstat(dstPath); err == nil {
|
||||
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
||||
if err := os.RemoveAll(dstPath); err != nil {
|
||||
if err := root.RemoveAll(dstPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -161,8 +179,8 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(path.Clean(hdr.Linkname), WhiteoutLinkDir) {
|
||||
linkBasename := path.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return 0, errors.New("invalid aufs hardlink")
|
||||
@@ -179,23 +197,23 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := createTarFile(dstPath, dest, srcHdr, srcData, options); err != nil {
|
||||
if err := createTarFile(root, dstPath, srcHdr, srcData, options); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
dirs = append(dirs, unpackedDir{hdr: hdr, name: dstPath})
|
||||
}
|
||||
unpackedPaths[dstPath] = struct{}{}
|
||||
// unpackedPaths is keyed by the POSIX (forward-slash) name so it
|
||||
// matches the ToSlash'd lookup in the opaque-whiteout walk above.
|
||||
unpackedPaths[hdr.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
||||
dstPath := filepath.Join(dest, hdr.Name)
|
||||
if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil {
|
||||
for _, d := range dirs {
|
||||
if err := root.Chtimes(d.name, boundTime(latestTime(d.hdr.AccessTime, d.hdr.ModTime)), boundTime(d.hdr.ModTime)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
112
vendor/github.com/moby/go-archive/rootpath.go
generated
vendored
Normal file
112
vendor/github.com/moby/go-archive/rootpath.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var errTooManyLinks = errors.New("too many links")
|
||||
|
||||
// fsRootPath joins a path with a root, evaluating and bounding any
|
||||
// symlink to the root directory.
|
||||
func fsRootPath(root, path string) (string, error) {
|
||||
if path == "" {
|
||||
return root, nil
|
||||
}
|
||||
var linksWalked int // to protect against cycles
|
||||
for {
|
||||
i := linksWalked
|
||||
newpath, err := walkLinks(root, path, &linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path = newpath
|
||||
if i == linksWalked {
|
||||
newpath = filepath.Join(string(os.PathSeparator), newpath)
|
||||
if path == newpath {
|
||||
return filepath.Join(root, newpath), nil
|
||||
}
|
||||
path = newpath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
|
||||
if *linksWalked > 255 {
|
||||
return "", false, errTooManyLinks
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
if path == string(os.PathSeparator) {
|
||||
return path, false, nil
|
||||
}
|
||||
realPath := filepath.Join(root, path)
|
||||
|
||||
fi, err := os.Lstat(realPath)
|
||||
if err != nil {
|
||||
// If path does not yet exist, treat as non-symlink
|
||||
if os.IsNotExist(err) {
|
||||
return path, false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
return path, false, nil
|
||||
}
|
||||
newpath, err = os.Readlink(realPath)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
*linksWalked++
|
||||
return newpath, true, nil
|
||||
}
|
||||
|
||||
func walkLinks(root, path string, linksWalked *int) (string, error) {
|
||||
switch dir, file := filepath.Split(path); {
|
||||
case dir == "":
|
||||
newpath, _, err := walkLink(root, file, linksWalked)
|
||||
return newpath, err
|
||||
case file == "":
|
||||
if os.IsPathSeparator(dir[len(dir)-1]) {
|
||||
if dir == string(os.PathSeparator) {
|
||||
return dir, nil
|
||||
}
|
||||
return walkLinks(root, dir[:len(dir)-1], linksWalked)
|
||||
}
|
||||
newpath, _, err := walkLink(root, dir, linksWalked)
|
||||
return newpath, err
|
||||
default:
|
||||
newdir, err := walkLinks(root, dir, linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !islink {
|
||||
return newpath, nil
|
||||
}
|
||||
if filepath.IsAbs(newpath) {
|
||||
return newpath, nil
|
||||
}
|
||||
return filepath.Join(newdir, newpath), nil
|
||||
}
|
||||
}
|
||||
6
vendor/github.com/moby/go-archive/sequential_other.go
generated
vendored
Normal file
6
vendor/github.com/moby/go-archive/sequential_other.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package archive
|
||||
|
||||
// windows_O_FILE_FLAG_SEQUENTIAL_SCAN is not supported on go < 1.26.
|
||||
const windows_O_FILE_FLAG_SEQUENTIAL_SCAN = 0
|
||||
9
vendor/github.com/moby/go-archive/sequential_windows_go126.go
generated
vendored
Normal file
9
vendor/github.com/moby/go-archive/sequential_windows_go126.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build windows && go1.26
|
||||
|
||||
package archive
|
||||
|
||||
// windows_O_FILE_FLAG_SEQUENTIAL_SCAN matches [golang.org/x/sys/windows.O_FILE_FLAG_SEQUENTIAL_SCAN].
|
||||
// Starting in Go 1.26, os.OpenFile supports passing this flag through.
|
||||
//
|
||||
// TODO(thaJeztah): use windows.O_FILE_FLAG_SEQUENTIAL_SCAN once we drop Go <1.26.
|
||||
const windows_O_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
|
||||
6
vendor/github.com/moby/go-archive/sequential_windows_pre126.go
generated
vendored
Normal file
6
vendor/github.com/moby/go-archive/sequential_windows_pre126.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build windows && !go1.26
|
||||
|
||||
package archive
|
||||
|
||||
// windows_O_FILE_FLAG_SEQUENTIAL_SCAN is not supported on go < 1.26.
|
||||
const windows_O_FILE_FLAG_SEQUENTIAL_SCAN = 0
|
||||
34
vendor/github.com/moby/go-archive/time_nonwindows.go
generated
vendored
34
vendor/github.com/moby/go-archive/time_nonwindows.go
generated
vendored
@@ -3,7 +3,12 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -28,14 +33,33 @@ func timeToTimespec(time time.Time) unix.Timespec {
|
||||
return unix.NsecToTimespec(time.UnixNano())
|
||||
}
|
||||
|
||||
func lchtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
func lchtimes(root *os.Root, name string, atime, mtime time.Time) error {
|
||||
dir, base := path.Split(filepath.ToSlash(name))
|
||||
if base == "" {
|
||||
return &os.PathError{Op: "lchtimes", Path: name, Err: syscall.EINVAL}
|
||||
}
|
||||
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
parent, err := root.Open(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
utimes := [2]unix.Timespec{
|
||||
timeToTimespec(atime),
|
||||
timeToTimespec(mtime),
|
||||
}
|
||||
err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil && err != unix.ENOSYS {
|
||||
return err
|
||||
// #nosec G115 -- ignore integer overflow conversion for parent.Fd
|
||||
if err := unix.UtimesNanoAt(int(parent.Fd()), base, utimes[:], unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
if errors.Is(err, unix.ENOSYS) {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{Op: "lchtimes", Path: name, Err: err}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/moby/go-archive/time_windows.go
generated
vendored
2
vendor/github.com/moby/go-archive/time_windows.go
generated
vendored
@@ -27,6 +27,6 @@ func chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
return windows.SetFileTime(h, &c, nil, nil)
|
||||
}
|
||||
|
||||
func lchtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
func lchtimes(root *os.Root, name string, atime time.Time, mtime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -1266,7 +1266,7 @@ github.com/moby/buildkit/worker/label
|
||||
# github.com/moby/docker-image-spec v1.3.1
|
||||
## explicit; go 1.18
|
||||
github.com/moby/docker-image-spec/specs-go/v1
|
||||
# github.com/moby/go-archive v0.2.1
|
||||
# github.com/moby/go-archive v0.2.2-0.20260724112411-2ff9bfb8b2ee
|
||||
## explicit; go 1.25
|
||||
github.com/moby/go-archive
|
||||
github.com/moby/go-archive/chrootarchive
|
||||
|
||||
Reference in New Issue
Block a user