From 2a3ab8fcc0c5e4b9bfce4577e7baddc842032298 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 4 Aug 2026 21:07:17 +0200 Subject: [PATCH] vendor: github.com/moby/go-archive v0.3.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix a regression introduced in v0.3.0 that caused archive extraction to reject hardlinks with absolute targets, as produced by some image builders. Absolute hardlink targets are now resolved relative to the extraction root, while paths that escape the root remain rejected. - Fix a regression introduced in v0.3.0 that caused archive extraction to fail when applying permissions to device nodes, including nodes on `nodev` filesystems and `dev/ptmx`. Device nodes are now referenced without opening the underlying device before applying their mode. - Set close-on-exec on file descriptors used by the Linux permission fallback to prevent them from leaking into child processes. full diff: https://github.com/moby/go-archive/compare/v0.3.2...v0.3.3 Signed-off-by: Paweł Gronowski --- daemon/archive_unix.go | 5 ++ go.mod | 2 +- go.sum | 4 +- vendor/github.com/moby/go-archive/archive.go | 26 ++++++++++- .../moby/go-archive/archive_linux.go | 18 ++++++++ .../moby/go-archive/archive_other.go | 8 ++++ .../moby/go-archive/archive_unix.go | 23 +++------- .../moby/go-archive/archive_windows.go | 2 +- .../github.com/moby/go-archive/chmod_linux.go | 46 +++++++++++++++++++ .../moby/go-archive/chmod_unix_nolinux.go | 27 +++++++++++ .../go-archive/chrootarchive/archive_linux.go | 20 ++++++-- .../internal/archiveoptions/options.go | 12 +++++ vendor/modules.txt | 3 +- 13 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 vendor/github.com/moby/go-archive/chmod_linux.go create mode 100644 vendor/github.com/moby/go-archive/chmod_unix_nolinux.go create mode 100644 vendor/github.com/moby/go-archive/internal/archiveoptions/options.go diff --git a/daemon/archive_unix.go b/daemon/archive_unix.go index b4777d8cae..a171587c4c 100644 --- a/daemon/archive_unix.go +++ b/daemon/archive_unix.go @@ -103,6 +103,11 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path defer container.Unlock() options := daemon.defaultTarCopyOptions(allowOverwriteDirWithFile) + options, cleanup, err := archive.WithProcSelfFD(options) + if err != nil { + return err + } + defer cleanup() // Decompress the archive before switching into the container's // filesystem to avoid executing decompression binaries (xz, unpigz) diff --git a/go.mod b/go.mod index a49bff133a..0b038d501b 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 github.com/moby/buildkit v0.32.2 github.com/moby/docker-image-spec v1.3.1 - github.com/moby/go-archive v0.3.2 + github.com/moby/go-archive v0.3.3 github.com/moby/ipvs v1.1.0 github.com/moby/locker v1.0.1 github.com/moby/moby/api v1.55.0 diff --git a/go.sum b/go.sum index 1102b93663..eb98500c1c 100644 --- a/go.sum +++ b/go.sum @@ -547,8 +547,8 @@ github.com/moby/buildkit v0.32.2 h1:Sfy7+u6dUv/2yuBc9KCoK70Re8atuV8aPZ5UOC068Vc= github.com/moby/buildkit v0.32.2/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8= 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.3.2 h1:x893kC3zRygv2C+k4Y9kMxYRPLCj4XEJB0srbAP06Hw= -github.com/moby/go-archive v0.3.2/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= +github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME= +github.com/moby/go-archive v0.3.3/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= diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go index f428fd3668..689720afaa 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -17,6 +17,7 @@ import ( "time" "github.com/containerd/log" + "github.com/moby/go-archive/internal/archiveoptions" "github.com/moby/patternmatcher" "github.com/moby/sys/sequential" "github.com/moby/sys/user" @@ -81,9 +82,22 @@ type ( // were probably in the archive for a reason, so set this option at // your own peril. BestEffortXattrs bool + + // internalOptions contains options for use by packages within this module. + internalOptions *archiveoptions.Options } ) +// WithProcSelfFD returns a copy of opts prepared for extraction in a +// filesystem context where /proc/self/fd may not be accessible by path. +// +// The caller must invoke the returned cleanup function after extraction +// completes. On platforms that do not use /proc/self/fd for extraction, +// the returned cleanup function is a no-op. +func WithProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) { + return withProcSelfFD(opts) +} + // Archiver implements the Archiver interface and allows the reuse of most utility functions of // this package with a pluggable Untar function. Also, to facilitate the passing of specific id // mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. @@ -509,6 +523,14 @@ func resolveArchivePath(root *os.Root, name string) (string, error) { // the native, root-relative filesystem path used for extraction. func resolveHardlinkTarget(root *os.Root, linkname string) (string, error) { cleaned := path.Clean(linkname) + if strings.HasPrefix(cleaned, "/") { + // Some image builders (e.g. kaniko) write hardlink targets as absolute + // paths. Resolve those relative to the extraction root, with chroot-like + // semantics matching absolute symlink targets. Strip the root from the + // original linkname rather than the cleaned one so that ".." components + // are not collapsed against "/" but instead rejected below. + cleaned = path.Clean(strings.TrimLeft(linkname, "/")) + } if cleaned == "." || !filepath.IsLocal(cleaned) { return "", breakoutError(fmt.Errorf("invalid hardlink target %q", linkname)) } @@ -523,6 +545,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea Lchown = true inUserns, bestEffortXattrs bool chownOpts *ChownOpts + internalOpts *archiveoptions.Options ) // TODO(thaJeztah): make opts a required argument. @@ -531,6 +554,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally. chownOpts = opts.ChownOpts bestEffortXattrs = opts.BestEffortXattrs + internalOpts = opts.internalOptions } // hdr.Mode is in linux format, which we can use for sycalls, @@ -672,7 +696,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea // 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(root, dstPath, hardlinkTarget, hdr, hdrInfo); err != nil { + if err := handleLChmod(root, dstPath, hardlinkTarget, hdr, hdrInfo, internalOpts); err != nil { return err } diff --git a/vendor/github.com/moby/go-archive/archive_linux.go b/vendor/github.com/moby/go-archive/archive_linux.go index 9341bc5997..c813cf9a7d 100644 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ b/vendor/github.com/moby/go-archive/archive_linux.go @@ -8,10 +8,28 @@ import ( "path/filepath" "strings" + "github.com/moby/go-archive/internal/archiveoptions" "github.com/moby/sys/userns" "golang.org/x/sys/unix" ) +func withProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) { + procSelfFD, err := os.Open("/proc/self/fd") + if err != nil { + return nil, nil, err + } + + var prepared TarOptions + if opts != nil { + prepared = *opts + } + prepared.internalOptions = &archiveoptions.Options{ + ProcSelfFD: procSelfFD, + } + + return &prepared, func() { _ = procSelfFD.Close() }, nil +} + func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { if format == OverlayWhiteoutFormat { return newOverlayWhiteoutConverter() diff --git a/vendor/github.com/moby/go-archive/archive_other.go b/vendor/github.com/moby/go-archive/archive_other.go index 6495549f60..c8fe043455 100644 --- a/vendor/github.com/moby/go-archive/archive_other.go +++ b/vendor/github.com/moby/go-archive/archive_other.go @@ -2,6 +2,14 @@ package archive +func withProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) { + var prepared TarOptions + if opts != nil { + prepared = *opts + } + return &prepared, func() {}, nil +} + func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { return nil } diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index 482ab69222..6b333e5b2c 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -12,6 +12,7 @@ import ( "strings" "syscall" + "github.com/moby/go-archive/internal/archiveoptions" "golang.org/x/sys/unix" ) @@ -87,7 +88,7 @@ func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, dstPath string) // 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, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo) error { +func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo, opts *archiveoptions.Options) error { switch hdr.Typeflag { case tar.TypeSymlink: return nil @@ -99,17 +100,17 @@ func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar if err != nil || fi.Mode()&os.ModeSymlink != 0 { return nil } - return chmodNoSymlink(root, dstPath, hdrInfo.Mode()) + return chmodNoSymlink(root, dstPath, hdrInfo.Mode(), opts) default: - return chmodNoSymlink(root, dstPath, hdrInfo.Mode()) + return chmodNoSymlink(root, dstPath, hdrInfo.Mode(), opts) } } // 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 { +func chmodNoSymlink(root *os.Root, name string, mode os.FileMode, opts *archiveoptions.Options) error { parent, err := root.OpenFile(filepath.Dir(name), os.O_RDONLY, 0) if err != nil { return err @@ -126,19 +127,7 @@ func chmodNoSymlink(root *os.Root, name string, mode os.FileMode) error { } // 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 + return chmodNoSymlinkFallback(int(parent.Fd()), base, name, perm, opts) // #nosec G115 -- ignore integer overflow conversion for parent.Fd } // fileModeToPerm returns the subset of an os.FileMode that can be applied diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go index aa9e523abc..a0e4336339 100644 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ b/vendor/github.com/moby/go-archive/archive_windows.go @@ -53,7 +53,7 @@ func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, path string) err } // handleLChmod is a no-op on Windows because chmod is not supported. -func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo) error { +func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo, opts any) error { return nil } diff --git a/vendor/github.com/moby/go-archive/chmod_linux.go b/vendor/github.com/moby/go-archive/chmod_linux.go new file mode 100644 index 0000000000..8d92f594ad --- /dev/null +++ b/vendor/github.com/moby/go-archive/chmod_linux.go @@ -0,0 +1,46 @@ +package archive + +import ( + "fmt" + "os" + "runtime" + "strconv" + + "github.com/moby/go-archive/internal/archiveoptions" + "golang.org/x/sys/unix" +) + +// chmodNoSymlinkFallback applies mode without following the final path +// component on systems without fchmodat2 support. +// +// Callers must have already excluded symlink entries. +func chmodNoSymlinkFallback(parentFD int, base, name string, perm uint32, opts *archiveoptions.Options) error { + fd, err := unix.Openat(parentFD, base, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return &os.PathError{Op: "openat", Path: name, Err: err} + } + defer unix.Close(fd) + + if opts != nil && opts.ProcSelfFD != nil { + err := unix.Fchmodat(int(opts.ProcSelfFD.Fd()), strconv.Itoa(fd), perm, 0) + // Keep the os.File alive until fchmodat has finished using its descriptor. + runtime.KeepAlive(opts.ProcSelfFD) + if err != nil { + return &os.PathError{ + Op: "fchmodat", + Path: name, + Err: fmt.Errorf("via pre-opened /proc/self/fd/%d: %w", fd, err), + } + } + } else { + procPath := "/proc/self/fd/" + strconv.Itoa(fd) + if err := unix.Chmod(procPath, perm); err != nil { + return &os.PathError{ + Op: "chmod", + Path: name, + Err: fmt.Errorf("via %s: %w", procPath, err), + } + } + } + return nil +} diff --git a/vendor/github.com/moby/go-archive/chmod_unix_nolinux.go b/vendor/github.com/moby/go-archive/chmod_unix_nolinux.go new file mode 100644 index 0000000000..0acddb1b98 --- /dev/null +++ b/vendor/github.com/moby/go-archive/chmod_unix_nolinux.go @@ -0,0 +1,27 @@ +//go:build !linux && !windows + +package archive + +import ( + "os" + + "github.com/moby/go-archive/internal/archiveoptions" + "golang.org/x/sys/unix" +) + +// chmodNoSymlinkFallback applies mode without following the final path +// component on systems without fchmodat2 support. +// +// Callers must have already excluded symlink entries. +func chmodNoSymlinkFallback(parentFD int, base, name string, perm uint32, _ *archiveoptions.Options) error { + fd, err := unix.Openat(parentFD, base, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 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 +} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go index a3b73676d2..de7b16acc6 100644 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go +++ b/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go @@ -9,9 +9,15 @@ import ( "github.com/moby/go-archive" ) -func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { +func doUnpack(decompressedArchive io.Reader, relDest, root string, opts *archive.TarOptions) error { + options, closeOptions, err := archive.WithProcSelfFD(opts) + if err != nil { + return err + } + defer closeOptions() + done := make(chan error) - err := goInChroot(root, func() { done <- archive.Unpack(decompressedArchive, relDest, options) }) + err = goInChroot(root, func() { done <- archive.Unpack(decompressedArchive, relDest, options) }) if err != nil { return err } @@ -30,14 +36,20 @@ func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, er return tb.Reader(), nil } -func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { +func doUnpackLayer(root string, layer io.Reader, opts *archive.TarOptions) (int64, error) { + options, closeOptions, err := archive.WithProcSelfFD(opts) + if err != nil { + return 0, err + } + defer closeOptions() + type result struct { layerSize int64 err error } done := make(chan result) - err := goInChroot(root, func() { + err = goInChroot(root, func() { // We need to be able to set any perms _ = unix.Umask(0) diff --git a/vendor/github.com/moby/go-archive/internal/archiveoptions/options.go b/vendor/github.com/moby/go-archive/internal/archiveoptions/options.go new file mode 100644 index 0000000000..201eb91b6d --- /dev/null +++ b/vendor/github.com/moby/go-archive/internal/archiveoptions/options.go @@ -0,0 +1,12 @@ +// Package archiveoptions defines internal options shared between archive and +// chrootarchive. +package archiveoptions + +import "os" + +// Options contains extraction resources supplied by internal callers. +type Options struct { + // ProcSelfFD references /proc/self/fd as opened before entering a chroot. + // The caller retains ownership of the file. + ProcSelfFD *os.File +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 19f70a2746..0705261e4e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1266,11 +1266,12 @@ 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.3.2 +# github.com/moby/go-archive v0.3.3 ## explicit; go 1.25 github.com/moby/go-archive github.com/moby/go-archive/chrootarchive github.com/moby/go-archive/compression +github.com/moby/go-archive/internal/archiveoptions github.com/moby/go-archive/internal/mounttree github.com/moby/go-archive/internal/unshare github.com/moby/go-archive/tarheader