From fada50cdaea8c7d9a9b622ddda9927c7b05e18db Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 17 Jul 2026 12:08:40 +0200 Subject: [PATCH 1/3] vendor: github.com/moby/go-archive main / v0.3.0-dev full diff: https://github.com/moby/go-archive/compare/v0.2.0...91b7e546daf2 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +- vendor/github.com/moby/go-archive/archive.go | 148 +++++++++--------- .../moby/go-archive/archive_linux.go | 19 +-- .../moby/go-archive/archive_unix.go | 31 ++-- .../moby/go-archive/archive_windows.go | 4 +- vendor/github.com/moby/go-archive/changes.go | 18 +-- .../moby/go-archive/changes_linux.go | 2 +- .../moby/go-archive/changes_other.go | 2 +- .../go-archive/compression/compression.go | 2 +- vendor/github.com/moby/go-archive/copy.go | 21 +-- vendor/github.com/moby/go-archive/diff.go | 20 +-- .../moby/go-archive/tarheader/tarheader.go | 2 +- .../moby/go-archive/xattr_supported.go | 20 +-- vendor/modules.txt | 4 +- 15 files changed, 157 insertions(+), 142 deletions(-) diff --git a/vendor.mod b/vendor.mod index 832b84b4a5f7..5d75cbe41ee9 100644 --- a/vendor.mod +++ b/vendor.mod @@ -31,7 +31,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/mattn/go-runewidth v0.0.24 - github.com/moby/go-archive v0.2.0 + github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2 // main / v0.3.0-dev github.com/moby/moby/api v1.55.0 github.com/moby/moby/client v0.5.0 github.com/moby/patternmatcher v0.6.1 diff --git a/vendor.sum b/vendor.sum index 6e0a010bd05e..4a2a6cedca90 100644 --- a/vendor.sum +++ b/vendor.sum @@ -111,8 +111,8 @@ github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= -github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2 h1:e1iD3zKmY2frfW5N7r1lp4tVZu5Qx2LB/LYUtzxUVeA= +github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2/go.mod h1:M+VLaXxS7DSgrQxxhQKGCuT9Wki/pa1hzMgU3VLQeHQ= github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go index 8dce2e6e2afa..fa2909793003 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -98,17 +98,17 @@ const ( // IsArchivePath checks if the (possibly compressed) file at the given path // starts with a tar file header. -func IsArchivePath(path string) bool { - file, err := os.Open(path) +func IsArchivePath(filePath string) bool { + file, err := os.Open(filePath) if err != nil { return false } - defer file.Close() + defer func() { _ = file.Close() }() rdr, err := compression.DecompressStream(file) if err != nil { return false } - defer rdr.Close() + defer func() { _ = rdr.Close() }() r := tar.NewReader(rdr) _, err = r.Next() return err == nil @@ -129,8 +129,10 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi go func() { tarReader := tar.NewReader(inputTarStream) tarWriter := tar.NewWriter(pipeWriter) - defer inputTarStream.Close() - defer tarWriter.Close() + defer func() { + _ = tarWriter.Close() + _ = inputTarStream.Close() + }() modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { header, data, err := modifier(name, original, tarReader) @@ -164,7 +166,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi break } if err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) return } @@ -172,11 +174,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi if !ok { // No modifiers for this file, copy the header and data if err := tarWriter.WriteHeader(originalHeader); err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) return } if err := copyWithBuffer(tarWriter, tarReader); err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) return } continue @@ -184,7 +186,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi delete(mods, originalHeader.Name) if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) return } } @@ -192,12 +194,12 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi // Apply the modifiers that haven't matched any files in the archive for name, modifier := range mods { if err := modify(name, nil, modifier, nil); err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) return } } - pipeWriter.Close() + _ = pipeWriter.Close() }() return pipeReader } @@ -227,7 +229,7 @@ const paxSchilyXattr = "SCHILY.xattr." // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem // to a tar header -func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { +func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error { const ( // Values based on linux/include/uapi/linux/capability.h xattrCapsSz2 = 20 @@ -235,7 +237,7 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { vfsCapRevision2 = 2 vfsCapRevision3 = 3 ) - capability, _ := lgetxattr(path, "security.capability") + capability, _ := lgetxattr(filePath, "security.capability") if capability != nil { if capability[versionOffset] == vfsCapRevision3 { // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no @@ -292,9 +294,10 @@ func canonicalTarName(name string, isDir bool) string { return name } -// addTarFile adds to the tar archive a file from `path` as `name` -func (ta *tarAppender) addTarFile(path, name string) error { - fi, err := os.Lstat(path) +// addTarFile adds to the tar archive a file from `srcPath` as `name` +func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { + archivePath = filepath.ToSlash(archivePath) + fi, err := os.Lstat(srcPath) if err != nil { return err } @@ -302,17 +305,17 @@ func (ta *tarAppender) addTarFile(path, name string) error { var link string if fi.Mode()&os.ModeSymlink != 0 { var err error - link, err = os.Readlink(path) + link, err = os.Readlink(srcPath) if err != nil { return err } } - hdr, err := FileInfoHeader(name, fi, link) + hdr, err := FileInfoHeader(archivePath, fi, link) if err != nil { return err } - if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { + if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil { return err } @@ -321,7 +324,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { if !fi.IsDir() && hasHardlinks(fi) { inode, err := getInodeFromStat(fi.Sys()) if err != nil { - return err + return fmt.Errorf("unexpected file info for %q: %w", srcPath, err) } // a link should have a name that it links too // and that linked name should be first in the tar archive @@ -330,7 +333,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { hdr.Linkname = oldpath hdr.Size = 0 // This Must be here for the writer math to add up! } else { - ta.SeenFiles[inode] = name + ta.SeenFiles[inode] = hdr.Name } } @@ -359,7 +362,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { } if ta.WhiteoutConverter != nil { - wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) + wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi) if err != nil { return err } @@ -387,13 +390,13 @@ func (ta *tarAppender) addTarFile(path, name string) error { if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { // We use sequential file access to avoid depleting the standby list on // Windows. On Linux, this equates to a regular os.Open. - file, err := sequential.Open(path) + file, err := sequential.Open(srcPath) if err != nil { return err } err = copyWithBuffer(ta.TarWriter, file) - file.Close() + _ = file.Close() if err != nil { return err } @@ -402,7 +405,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { return nil } -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { +func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { var ( Lchown = true inUserns, bestEffortXattrs bool @@ -426,8 +429,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o 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(path); err != nil || !fi.IsDir() { - if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { + if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() { + if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil { return err } } @@ -435,7 +438,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o 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(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) + file, err := sequential.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) if err != nil { return err } @@ -447,20 +450,20 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o case tar.TypeBlock, tar.TypeChar: if inUserns { // cannot create devices in a userns - log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") + 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, path); err != nil { + if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { return err } case tar.TypeFifo: // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { + if err := handleTarTypeBlockCharFifo(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": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") + log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": dstPath, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") return nil } return err @@ -468,26 +471,26 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o case tar.TypeLink: // #nosec G305 -- The target path is checked for path traversal. - targetPath := filepath.Join(extractDir, hdr.Linkname) + linkTarget := filepath.Join(extractDir, hdr.Linkname) // check for hardlink breakout - if !strings.HasPrefix(targetPath, extractDir) { - return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) + if !strings.HasPrefix(linkTarget, extractDir) { + return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", linkTarget, hdr.Linkname)) } - if err := os.Link(targetPath, path); err != nil { + if err := os.Link(linkTarget, 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(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. + targetPath := filepath.Join(filepath.Dir(dstPath), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. // 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", path, hdr.Linkname)) + return breakoutError(fmt.Errorf("invalid symlink %q -> %q", dstPath, hdr.Linkname)) } - if err := os.Symlink(hdr.Linkname, path); err != nil { + if err := os.Symlink(hdr.Linkname, dstPath); err != nil { return err } @@ -504,12 +507,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o if chownOpts == nil { chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid} } - if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { + if err := os.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)" } - return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err) + return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", dstPath, hdr.Uid, hdr.Gid, msg, err) } } @@ -519,7 +522,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o if !ok { continue } - if err := lsetxattr(path, xattr, []byte(value), 0); err != nil { + if err := lsetxattr(dstPath, 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). @@ -538,7 +541,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o // 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, path, hdrInfo); err != nil { + if err := handleLChmod(hdr, dstPath, hdrInfo); err != nil { return err } @@ -548,29 +551,29 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o // chtimes doesn't support a NOFOLLOW flag atm if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := chtimes(path, aTime, mTime); err != nil { + if err := chtimes(dstPath, aTime, mTime); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := chtimes(path, aTime, mTime); err != nil { + if err := chtimes(dstPath, aTime, mTime); err != nil { return err } } else { - if err := lchtimes(path, aTime, mTime); err != nil { + if err := lchtimes(dstPath, aTime, mTime); err != nil { return err } } return nil } -// Tar creates an archive from the directory at `path`, and returns it as a +// Tar creates an archive from the directory at `srcPath`, and returns it as a // stream of bytes. -func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { - return TarWithOptions(path, &TarOptions{Compression: comp}) +func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) { + return TarWithOptions(srcPath, &TarOptions{Compression: comp}) } -// TarWithOptions creates an archive from the directory at `path`, only including files whose relative +// TarWithOptions creates an archive from the directory at `srcPath`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { tb, err := NewTarballer(srcPath, options) @@ -846,8 +849,8 @@ loop: } // #nosec G305 -- The joined path is checked for path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) + dstPath := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, dstPath) if err != nil { return err } @@ -859,17 +862,17 @@ loop: // 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(path); err == nil { + if fi, err := os.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", path, dest) + return fmt.Errorf("cannot overwrite directory %q with non-directory %q", dstPath, 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", path, dest) + return fmt.Errorf("cannot overwrite non-directory %q with directory %q", dstPath, dest) } if fi.IsDir() && hdr.Name == "." { @@ -877,7 +880,7 @@ loop: } if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { + if err := os.RemoveAll(dstPath); err != nil { return err } } @@ -888,7 +891,7 @@ loop: } if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, path) + writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath) if err != nil { return err } @@ -897,7 +900,7 @@ loop: } } - if err := createTarFile(path, dest, hdr, tr, options); err != nil { + if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil { return err } @@ -910,9 +913,8 @@ loop: for _, hdr := range dirs { // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - path := filepath.Join(dest, hdr.Name) - - if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { + dstPath := filepath.Join(dest, hdr.Name) + if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { return err } } @@ -923,13 +925,9 @@ loop: // 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. -// -// The caller should have performed filepath.Clean(hdr.Name), so hdr.Name will now be in the filepath format for the OS -// on which the daemon is running. This precondition is required because this function assumes a OS-specific path -// separator when checking that a path is not the root. func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error { - // Not the root directory, ensure that the parent directory exists - if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { + // 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) { @@ -984,7 +982,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp if err != nil { return err } - defer decompressedArchive.Close() + defer func() { _ = decompressedArchive.Close() }() r = decompressedArchive } @@ -998,7 +996,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error { if err != nil { return err } - defer archive.Close() + defer func() { _ = archive.Close() }() return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, }) @@ -1010,7 +1008,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error { if err != nil { return err } - defer archive.Close() + defer func() { _ = archive.Close() }() return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, }) @@ -1070,13 +1068,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { defer close(errC) errC <- func() error { - defer w.Close() + defer func() { _ = w.Close() }() srcF, err := os.Open(src) if err != nil { return err } - defer srcF.Close() + defer func() { _ = srcF.Close() }() hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") if err != nil { @@ -1094,7 +1092,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { } tw := tar.NewWriter(w) - defer tw.Close() + defer func() { _ = tw.Close() }() if err := tw.WriteHeader(hdr); err != nil { return err } @@ -1112,7 +1110,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { err = archiver.Untar(r, filepath.Dir(dst), nil) if err != nil { - r.CloseWithError(err) + _ = r.CloseWithError(err) } 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 7b6c3e02b643..9ef69b078f45 100644 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ b/vendor/github.com/moby/go-archive/archive_linux.go @@ -4,6 +4,7 @@ import ( "archive/tar" "fmt" "os" + "path" "path/filepath" "strings" @@ -20,18 +21,18 @@ func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { type overlayWhiteoutConverter struct{} -func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) { +func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ error) { // convert whiteouts to AUFS format if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { // we just rename the file and make it normal - dir, filename := filepath.Split(hdr.Name) - hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) + dir, filename := path.Split(hdr.Name) + hdr.Name = path.Join(dir, WhiteoutPrefix+filename) hdr.Mode = 0o600 hdr.Typeflag = tar.TypeReg hdr.Size = 0 } - if fi.Mode()&os.ModeDir == 0 { + if !fi.IsDir() { // FIXME(thaJeztah): return a sentinel error instead of nil, nil return nil, nil } @@ -42,7 +43,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os } // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(path, opaqueXattrName) + opaque, err := lgetxattr(filePath, opaqueXattrName) if err != nil { return nil, err } @@ -57,7 +58,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os return &tar.Header{ Typeflag: tar.TypeReg, Mode: hdr.Mode & int64(os.ModePerm), - Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. + Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. Size: 0, Uid: hdr.Uid, Uname: hdr.Uname, @@ -68,9 +69,9 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os }, nil } -func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { - base := filepath.Base(path) - dir := filepath.Dir(path) +func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) { + base := filepath.Base(filePath) + dir := filepath.Dir(filePath) // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay if base == WhiteoutOpaqueDir { diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index 3a9f5b0b5863..5c6e4a9920a6 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -5,6 +5,8 @@ package archive import ( "archive/tar" "errors" + "fmt" + "math" "os" "path/filepath" "strings" @@ -13,6 +15,8 @@ import ( "golang.org/x/sys/unix" ) +var errInvalidArchive = errors.New("invalid archive") + // addLongPathPrefix adds the Windows long path prefix to the path provided if // it does not already have it. It is a no-op on platforms other than Windows. func addLongPathPrefix(srcPath string) string { @@ -33,16 +37,15 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm // noop for unix as golang APIs provide perm bits correctly } -func getInodeFromStat(stat interface{}) (uint64, error) { +func getInodeFromStat(stat any) (uint64, error) { s, ok := stat.(*syscall.Stat_t) if !ok { - // FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897 - return 0, nil + return 0, fmt.Errorf("unexpected stat type %T", stat) } return s.Ino, nil } -func getFileUIDGID(stat interface{}) (int, int, error) { +func getFileUIDGID(stat any) (int, int, error) { s, ok := stat.(*syscall.Stat_t) if !ok { @@ -56,7 +59,7 @@ func getFileUIDGID(stat interface{}) (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, path string) error { +func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { mode := uint32(hdr.Mode & 0o7777) switch hdr.Typeflag { case tar.TypeBlock: @@ -67,18 +70,28 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { mode |= unix.S_IFIFO } - return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) + // Devmajor and Devminor come straight from the (untrusted) tar header as + // int64, but Mkdev only takes uint32. Casting a value that does not fit + // silently truncates it, so the node created on disk would carry a + // different major/minor than the header declares. Reject those instead of + // creating a mismatched device. + if hdr.Devmajor < 0 || hdr.Devmajor > math.MaxUint32 || + hdr.Devminor < 0 || hdr.Devminor > math.MaxUint32 { + 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))) } -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { +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(path, hdrInfo.Mode()); err != nil { + if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { + if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { return err } } diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go index 0e3e316afc2f..208f6d07b655 100644 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ b/vendor/github.com/moby/go-archive/archive_windows.go @@ -41,7 +41,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm | 0o111 } -func getInodeFromStat(stat interface{}) (uint64, error) { +func getInodeFromStat(stat any) (uint64, error) { // do nothing. no notion of Inode in stat on Windows return 0, nil } @@ -56,7 +56,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return nil } -func getFileUIDGID(stat interface{}) (int, int, error) { +func getFileUIDGID(stat any) (int, int, error) { // no notion of file ownership mapping yet on Windows return 0, 0, nil } diff --git a/vendor/github.com/moby/go-archive/changes.go b/vendor/github.com/moby/go-archive/changes.go index 02a0372c6f99..c5d647c030d2 100644 --- a/vendor/github.com/moby/go-archive/changes.go +++ b/vendor/github.com/moby/go-archive/changes.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "maps" "os" "path/filepath" "sort" @@ -217,8 +218,8 @@ func (info *FileInfo) LookUp(path string) *FileInfo { return info } - pathElements := strings.Split(path, string(os.PathSeparator)) - for _, elem := range pathElements { + pathElements := strings.SplitSeq(path, string(os.PathSeparator)) + for elem := range pathElements { if elem != "" { child := parent.children[elem] if child == nil { @@ -256,9 +257,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { // otherwise any previous delete/change is considered recursive oldChildren := make(map[string]*FileInfo) if oldInfo != nil && info.isDir() { - for k, v := range oldInfo.children { - oldChildren[k] = v - } + maps.Copy(oldChildren, oldInfo.children) } for name, newChild := range info.children { @@ -401,7 +400,7 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) timestamp := time.Now() hdr := &tar.Header{ - Name: whiteOut[1:], + Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"), Size: 0, ModTime: timestamp, AccessTime: timestamp, @@ -411,9 +410,10 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err) } } else { - path := filepath.Join(dir, change.Path) - if err := ta.addTarFile(path, change.Path[1:]); err != nil { - log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err) + srcPath := filepath.Join(dir, change.Path) + archivePath := strings.TrimPrefix(filepath.ToSlash(change.Path), "/") + if err := ta.addTarFile(srcPath, archivePath); err != nil { + log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", srcPath, err) } } } diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go index 8289fe17d906..39ec7475f4ea 100644 --- a/vendor/github.com/moby/go-archive/changes_linux.go +++ b/vendor/github.com/moby/go-archive/changes_linux.go @@ -265,7 +265,7 @@ func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) } func clen(n []byte) int { - for i := 0; i < len(n); i++ { + for i := range n { if n[i] == 0 { return i } diff --git a/vendor/github.com/moby/go-archive/changes_other.go b/vendor/github.com/moby/go-archive/changes_other.go index a8a3a5a6faa8..7eb195d500ec 100644 --- a/vendor/github.com/moby/go-archive/changes_other.go +++ b/vendor/github.com/moby/go-archive/changes_other.go @@ -26,7 +26,7 @@ func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, err }() // block until both routines have returned - for i := 0; i < 2; i++ { + for range 2 { if err := <-errs; err != nil { return nil, nil, err } diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go index e298cefb3249..c3ed79f92860 100644 --- a/vendor/github.com/moby/go-archive/compression/compression.go +++ b/vendor/github.com/moby/go-archive/compression/compression.go @@ -66,7 +66,7 @@ type nopWriteCloser struct { func (nopWriteCloser) Close() error { return nil } var bufioReader32KPool = &sync.Pool{ - New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, + New: func() any { return bufio.NewReaderSize(nil, 32*1024) }, } type bufferedReader struct { diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go index 77d038c42310..6ef917b63e4f 100644 --- a/vendor/github.com/moby/go-archive/copy.go +++ b/vendor/github.com/moby/go-archive/copy.go @@ -22,7 +22,7 @@ var ( ) var copyPool = sync.Pool{ - New: func() interface{} { s := make([]byte, 32*1024); return &s }, + New: func() any { s := make([]byte, 32*1024); return &s }, } func copyWithBuffer(dst io.Writer, src io.Reader) error { @@ -319,7 +319,10 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir // RebaseArchiveEntries rewrites the given srcContent archive replacing // an occurrence of oldBase with newBase at the beginning of entry names. func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser { - if oldBase == string(os.PathSeparator) { + oldBase = filepath.ToSlash(oldBase) + newBase = filepath.ToSlash(newBase) + + if oldBase == "/" { // If oldBase specifies the root directory, use an empty string as // oldBase instead so that newBase doesn't replace the path separator // that all paths will start with. @@ -336,12 +339,12 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read hdr, err := srcTar.Next() if errors.Is(err, io.EOF) { // Signals end of archive. - rebasedTar.Close() - w.Close() + _ = rebasedTar.Close() + _ = w.Close() return } if err != nil { - w.CloseWithError(err) + _ = w.CloseWithError(err) return } @@ -359,7 +362,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read } if err = rebasedTar.WriteHeader(hdr); err != nil { - w.CloseWithError(err) + _ = w.CloseWithError(err) return } @@ -374,7 +377,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read // not be vulnerable to this code consuming memory. //nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec) if _, err = io.Copy(rebasedTar, srcTar); err != nil { - w.CloseWithError(err) + _ = w.CloseWithError(err) return } } @@ -408,7 +411,7 @@ func CopyResource(srcPath, dstPath string, followLink bool) error { if err != nil { return err } - defer content.Close() + defer func() { _ = content.Close() }() return CopyTo(content, srcInfo, dstPath) } @@ -427,7 +430,7 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { if err != nil { return err } - defer copyArchive.Close() + defer func() { _ = copyArchive.Close() }() options := &TarOptions{ NoLchown: true, diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go index 96db972d1383..cde527837e6f 100644 --- a/vendor/github.com/moby/go-archive/diff.go +++ b/vendor/github.com/moby/go-archive/diff.go @@ -102,8 +102,8 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, } } // #nosec G305 -- The joined path is guarded against path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) + dstPath := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, dstPath) if err != nil { return 0, err } @@ -112,10 +112,10 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } - base := filepath.Base(path) + base := filepath.Base(dstPath) if strings.HasPrefix(base, WhiteoutPrefix) { - dir := filepath.Dir(path) + dir := filepath.Dir(dstPath) if base == WhiteoutOpaqueDir { _, err := os.Lstat(dir) if err != nil { @@ -151,9 +151,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(path); err == nil { + if fi, err := os.Lstat(dstPath); err == nil { if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { + if err := os.RemoveAll(dstPath); err != nil { return 0, err } } @@ -182,7 +182,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, return 0, err } - if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil { + if err := createTarFile(dstPath, dest, srcHdr, srcData, options); err != nil { return 0, err } @@ -191,14 +191,14 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } - unpackedPaths[path] = struct{}{} + unpackedPaths[dstPath] = struct{}{} } } for _, hdr := range dirs { // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - path := filepath.Join(dest, hdr.Name) - if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { + dstPath := filepath.Join(dest, hdr.Name) + if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil { return 0, err } } diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader.go b/vendor/github.com/moby/go-archive/tarheader/tarheader.go index 03732a4f844b..78ab55cd410f 100644 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader.go +++ b/vendor/github.com/moby/go-archive/tarheader/tarheader.go @@ -32,7 +32,7 @@ func (fi nosysFileInfo) Gname() (string, error) { return "", nil } -func (fi nosysFileInfo) Sys() interface{} { +func (fi nosysFileInfo) Sys() any { // A Sys value of type *tar.Header is safe as it is system-independent. // The tar.FileInfoHeader function copies the fields into the returned // header without performing any OS lookups. diff --git a/vendor/github.com/moby/go-archive/xattr_supported.go b/vendor/github.com/moby/go-archive/xattr_supported.go index 652a1f0f349d..8478fe1446da 100644 --- a/vendor/github.com/moby/go-archive/xattr_supported.go +++ b/vendor/github.com/moby/go-archive/xattr_supported.go @@ -13,26 +13,26 @@ import ( // lgetxattr retrieves the value of the extended attribute identified by attr // and associated with the given path in the file system. // It returns a nil slice and nil error if the xattr is not set. -func lgetxattr(path string, attr string) ([]byte, error) { +func lgetxattr(filePath string, attr string) ([]byte, error) { // Start with a 128 length byte array dest := make([]byte, 128) - sz, err := unix.Lgetxattr(path, attr, dest) + sz, err := unix.Lgetxattr(filePath, attr, dest) for errors.Is(err, unix.ERANGE) { // Buffer too small, use zero-sized buffer to get the actual size - sz, err = unix.Lgetxattr(path, attr, []byte{}) + sz, err = unix.Lgetxattr(filePath, attr, []byte{}) if err != nil { - return nil, wrapPathError("lgetxattr", path, attr, err) + return nil, wrapPathError("lgetxattr", filePath, attr, err) } dest = make([]byte, sz) - sz, err = unix.Lgetxattr(path, attr, dest) + sz, err = unix.Lgetxattr(filePath, attr, dest) } if err != nil { if errors.Is(err, noattr) { return nil, nil } - return nil, wrapPathError("lgetxattr", path, attr, err) + return nil, wrapPathError("lgetxattr", filePath, attr, err) } return dest[:sz], nil @@ -40,13 +40,13 @@ func lgetxattr(path string, attr string) ([]byte, error) { // lsetxattr sets the value of the extended attribute identified by attr // and associated with the given path in the file system. -func lsetxattr(path string, attr string, data []byte, flags int) error { - return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags)) +func lsetxattr(filePath string, attr string, data []byte, flags int) error { + return wrapPathError("lsetxattr", filePath, attr, unix.Lsetxattr(filePath, attr, data, flags)) } -func wrapPathError(op, path, attr string, err error) error { +func wrapPathError(op, filePath, attr string, err error) error { if err == nil { return nil } - return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)} + return &fs.PathError{Op: op, Path: filePath, Err: fmt.Errorf("xattr %q: %w", attr, err)} } diff --git a/vendor/modules.txt b/vendor/modules.txt index c924f430f753..75e6a6ee9f71 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -162,8 +162,8 @@ github.com/mattn/go-runewidth # 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.0 -## explicit; go 1.23.0 +# github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2 +## explicit; go 1.25 github.com/moby/go-archive github.com/moby/go-archive/compression github.com/moby/go-archive/tarheader From ae768a7a988e50a41ee3a7c62422634464d3292c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 17 Jul 2026 12:09:51 +0200 Subject: [PATCH 2/3] vendor: github.com/moby/go-archive with hardening full diff: https://github.com/ctalledo/go-archive/compare/91b7e546daf2...df2ce6219bdc Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 3 + vendor.sum | 4 +- vendor/github.com/moby/go-archive/archive.go | 257 ++++++++++++------ .../moby/go-archive/archive_unix.go | 11 +- .../moby/go-archive/archive_windows.go | 3 +- vendor/github.com/moby/go-archive/diff.go | 126 +++++---- vendor/github.com/moby/go-archive/rootpath.go | 112 ++++++++ .../moby/go-archive/sequential_other.go | 6 + .../go-archive/sequential_windows_go126.go | 9 + .../go-archive/sequential_windows_pre126.go | 6 + .../moby/go-archive/time_nonwindows.go | 29 +- .../moby/go-archive/time_windows.go | 2 +- vendor/modules.txt | 3 +- 13 files changed, 427 insertions(+), 144 deletions(-) create mode 100644 vendor/github.com/moby/go-archive/rootpath.go create mode 100644 vendor/github.com/moby/go-archive/sequential_other.go create mode 100644 vendor/github.com/moby/go-archive/sequential_windows_go126.go create mode 100644 vendor/github.com/moby/go-archive/sequential_windows_pre126.go diff --git a/vendor.mod b/vendor.mod index 5d75cbe41ee9..5dc3255799fd 100644 --- a/vendor.mod +++ b/vendor.mod @@ -11,6 +11,9 @@ tool ( golang.org/x/mod/modfile // for module compatibility check ) +// FIXME(thaJeztah): testing https://github.com/moby/go-archive/pull/45 +replace github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc + require ( dario.cat/mergo v1.0.2 github.com/containerd/errdefs v1.0.0 diff --git a/vendor.sum b/vendor.sum index 4a2a6cedca90..6731e4502f8e 100644 --- a/vendor.sum +++ b/vendor.sum @@ -31,6 +31,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3 github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc h1:MEQg/nICbusB9kvrXFuhn9Q7ZNsKoYK7ss7NkgVvS/4= +github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc/go.mod h1:M+VLaXxS7DSgrQxxhQKGCuT9Wki/pa1hzMgU3VLQeHQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -111,8 +113,6 @@ github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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-0.20260716170833-91b7e546daf2 h1:e1iD3zKmY2frfW5N7r1lp4tVZu5Qx2LB/LYUtzxUVeA= -github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2/go.mod h1:M+VLaXxS7DSgrQxxhQKGCuT9Wki/pa1hzMgU3VLQeHQ= github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go index fa2909793003..8df35ed4e080 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -8,9 +8,11 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "runtime" "strings" + "sync" "syscall" "time" @@ -344,7 +346,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 @@ -405,7 +407,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 @@ -425,20 +430,34 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader // so use hdrInfo.Mode() (they differ for e.g. setuid bits) hdrInfo := hdr.FileInfo() + // absPath is computed lazily: most types use dc methods (backed by *at(2) + // syscalls) and never need it. It is only required for mknod and xattrs. + // Symlinks intentionally use root.Symlink directly to preserve absolute + // targets; os.Root.Symlink rejects absolute targets like /usr/lib. + absPath := sync.OnceValues(func() (string, error) { + return fsRootPath(root.Name(), dstPath) + }) + 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 } @@ -453,14 +472,22 @@ 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 { + // os.Root has no mknod support; use absPath so the path stays bounded. + ap, err := absPath() + if err != nil { + return err + } + if err := handleTarTypeBlockCharFifo(hdr, ap); err != nil { return err } case tar.TypeFifo: - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { + // os.Root has no mknod support; use absPath so the path stays bounded. + ap, err := absPath() + if err != nil { + return err + } + if err := handleTarTypeBlockCharFifo(hdr, ap); 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") @@ -470,27 +497,24 @@ 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. + if !filepath.IsLocal(hdr.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(hdr.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. - - // 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(hdr.Linkname, dstPath); err != nil { return err } @@ -507,7 +531,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)" @@ -522,7 +546,13 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader 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). @@ -541,26 +571,26 @@ 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 } aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) mTime := boundTime(hdr.ModTime) - // chtimes doesn't support a NOFOLLOW flag atm + // Symlinks use lchtimes (AT_SYMLINK_NOFOLLOW); all other types follow symlinks. if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := chtimes(dstPath, aTime, mTime); err != nil { + if fi, err := root.Lstat(filepath.FromSlash(hdr.Linkname)); err == nil && (fi.Mode()&os.ModeSymlink == 0) { + if err := root.Chtimes(dstPath, aTime, mTime); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := chtimes(dstPath, aTime, mTime); err != nil { + if err := root.Chtimes(dstPath, aTime, mTime); err != nil { return err } } else { - if err := lchtimes(dstPath, aTime, mTime); err != nil { + if err := lchtimes(root, dstPath, aTime, mTime); err != nil { return err } } @@ -806,11 +836,25 @@ 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 { + root, err := os.OpenRoot(dest) + if err != nil { + return err + } + defer root.Close() + tr := tar.NewReader(decompressedArchive) - var dirs []*tar.Header + var dirs []unpackedDir whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) // Iterate through the files in the archive. @@ -831,10 +875,24 @@ 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, then + // Clean while keeping any ".." so the IsLocal check below rejects + // escapes instead of silently rewriting them. + hdr.Name = path.Clean(strings.TrimLeft(hdr.Name, "/")) + // Reject names that escape the extraction root (absolute or ".."). + if !filepath.IsLocal(hdr.Name) { + return breakoutError(fmt.Errorf("invalid entry name %q", hdr.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 + } + + // 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) for _, exclude := range options.ExcludePatterns { if strings.HasPrefix(hdr.Name, exclude) { @@ -843,36 +901,26 @@ loop: } // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return err - } - - // #nosec G305 -- The joined path is checked for path traversal. - dstPath := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, dstPath) + err = createImpliedDirectories(root, hdr, options) if err != nil { return err } - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - // If path exits we almost always just want to remove and replace it + // If dstPath exits 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 == "." { @@ -880,7 +928,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 } } @@ -891,7 +939,14 @@ loop: } if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath) + // ConvertRead implementations (e.g. overlayWhiteoutConverter) + // make direct syscalls with the path, so they need the absolute + // path bounded within dest rather than the root-relative name. + absPath, err := fsRootPath(root.Name(), dstPath) + if err != nil { + return err + } + writeFile, err := whiteoutConverter.ConvertRead(hdr, absPath) if err != nil { return err } @@ -900,46 +955,96 @@ 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) { - // 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) - if err != nil { + 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() + + // 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) { + continue + } return err } + + // Only the successful Mkdir case is newly-created. + if uid != 0 || gid != 0 { + if err := root.Lchown(cur, uid, gid); err != nil { + return err + } + } } } diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index 5c6e4a9920a6..a70ae6469bd8 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -83,15 +83,18 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) } -func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error { +// 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 { 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 { + if fi, err := root.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { + if err := root.Chmod(dstPath, hdrInfo.Mode()); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { + if err := root.Chmod(dstPath, hdrInfo.Mode()); err != nil { return err } } diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go index 208f6d07b655..6a81388a0da9 100644 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ b/vendor/github.com/moby/go-archive/archive_windows.go @@ -52,7 +52,8 @@ func handleTarTypeBlockCharFifo(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 } diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go index cde527837e6f..3bdde513c36f 100644 --- a/vendor/github.com/moby/go-archive/diff.go +++ b/vendor/github.com/moby/go-archive/diff.go @@ -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 { @@ -48,32 +56,23 @@ 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, then + // Clean while keeping any ".." so the IsLocal check below rejects + // escapes instead of silently rewriting them. + hdr.Name = path.Clean(strings.TrimLeft(hdr.Name, "/")) + // Reject names that escape the extraction root (absolute or ".."). + if !filepath.IsLocal(hdr.Name) { + return 0, breakoutError(fmt.Errorf("invalid entry name %q", hdr.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 } @@ -84,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 { @@ -92,47 +91,64 @@ 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 + } + // 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(dir, func(path string, info os.DirEntry, err error) error { + 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 err } - if path == dir { + if p == absDir { return nil } - if _, exists := unpackedPaths[path]; !exists { - return os.RemoveAll(path) + // unpackedPaths is keyed by the root-relative, forward-slash + // form of hdr.Name (the result of path.Join("/", ...) in + // the normalisation step above). filepath.WalkDir yields + // OS-native separators, so convert to slash form before + // looking up so the comparison works on Windows too. + rel := filepath.ToSlash(strings.TrimPrefix( + p, root.Name()+string(os.PathSeparator), + )) + if _, exists := unpackedPaths[rel]; !exists { + return os.RemoveAll(p) } return nil }) @@ -142,18 +158,18 @@ 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 } } } else { - // If path exits we almost always just want to remove and replace it. + // If dstPath exits 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 !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(dstPath); err != nil { + if err := root.RemoveAll(dstPath); err != nil { return 0, err } } @@ -164,8 +180,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") @@ -182,23 +198,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, d.hdr.AccessTime, d.hdr.ModTime); err != nil { return 0, err } } diff --git a/vendor/github.com/moby/go-archive/rootpath.go b/vendor/github.com/moby/go-archive/rootpath.go new file mode 100644 index 000000000000..fdf605ffce4a --- /dev/null +++ b/vendor/github.com/moby/go-archive/rootpath.go @@ -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("/", 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("/", path) + if path == "/" { + 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 == "/" { + 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 + } +} diff --git a/vendor/github.com/moby/go-archive/sequential_other.go b/vendor/github.com/moby/go-archive/sequential_other.go new file mode 100644 index 000000000000..90edb13933d2 --- /dev/null +++ b/vendor/github.com/moby/go-archive/sequential_other.go @@ -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 diff --git a/vendor/github.com/moby/go-archive/sequential_windows_go126.go b/vendor/github.com/moby/go-archive/sequential_windows_go126.go new file mode 100644 index 000000000000..1e80d11b73ea --- /dev/null +++ b/vendor/github.com/moby/go-archive/sequential_windows_go126.go @@ -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 diff --git a/vendor/github.com/moby/go-archive/sequential_windows_pre126.go b/vendor/github.com/moby/go-archive/sequential_windows_pre126.go new file mode 100644 index 000000000000..2b281741ae12 --- /dev/null +++ b/vendor/github.com/moby/go-archive/sequential_windows_pre126.go @@ -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 diff --git a/vendor/github.com/moby/go-archive/time_nonwindows.go b/vendor/github.com/moby/go-archive/time_nonwindows.go index 5bfdfa2f17e8..59592a44b86a 100644 --- a/vendor/github.com/moby/go-archive/time_nonwindows.go +++ b/vendor/github.com/moby/go-archive/time_nonwindows.go @@ -3,7 +3,12 @@ package archive import ( + "errors" "os" + "path" + "path/filepath" + "strings" + "syscall" "time" "golang.org/x/sys/unix" @@ -28,14 +33,30 @@ 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 + err = unix.UtimesNanoAt(int(parent.Fd()), base, utimes[:], unix.AT_SYMLINK_NOFOLLOW) + if err != nil && !errors.Is(err, unix.ENOSYS) { + return &os.PathError{Op: "lchtimes", Path: name, Err: err} } return err } diff --git a/vendor/github.com/moby/go-archive/time_windows.go b/vendor/github.com/moby/go-archive/time_windows.go index af1f7c8f3a07..c4a007fb7a56 100644 --- a/vendor/github.com/moby/go-archive/time_windows.go +++ b/vendor/github.com/moby/go-archive/time_windows.go @@ -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 } diff --git a/vendor/modules.txt b/vendor/modules.txt index 75e6a6ee9f71..123162d6d799 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -162,7 +162,7 @@ github.com/mattn/go-runewidth # 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-0.20260716170833-91b7e546daf2 +# github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2 => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc ## explicit; go 1.25 github.com/moby/go-archive github.com/moby/go-archive/compression @@ -565,3 +565,4 @@ gotest.tools/v3/skip # tags.cncf.io/container-device-interface v1.1.0 ## explicit; go 1.21 tags.cncf.io/container-device-interface/pkg/parser +# github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc From 6ab715748a517e3a14221aaeea9d5112e590f2e5 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 17 Jul 2026 13:37:08 +0200 Subject: [PATCH 3/3] update to 2bc39549db4eb6a64d20db622a1223fe653e1443 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +- vendor/github.com/moby/go-archive/archive.go | 26 ++++++++---- .../moby/go-archive/archive_unix.go | 21 +++++----- vendor/github.com/moby/go-archive/diff.go | 41 +++++++++++-------- .../moby/go-archive/time_nonwindows.go | 8 ++-- vendor/modules.txt | 4 +- 7 files changed, 63 insertions(+), 43 deletions(-) diff --git a/vendor.mod b/vendor.mod index 5dc3255799fd..4583249692e3 100644 --- a/vendor.mod +++ b/vendor.mod @@ -12,7 +12,7 @@ tool ( ) // FIXME(thaJeztah): testing https://github.com/moby/go-archive/pull/45 -replace github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc +replace github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260717121834-2bc39549db4e require ( dario.cat/mergo v1.0.2 diff --git a/vendor.sum b/vendor.sum index 6731e4502f8e..1f4d1c7218b6 100644 --- a/vendor.sum +++ b/vendor.sum @@ -31,8 +31,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3 github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc h1:MEQg/nICbusB9kvrXFuhn9Q7ZNsKoYK7ss7NkgVvS/4= -github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc/go.mod h1:M+VLaXxS7DSgrQxxhQKGCuT9Wki/pa1hzMgU3VLQeHQ= +github.com/ctalledo/go-archive v0.2.1-0.20260717121834-2bc39549db4e h1:vsfL4iiXaufHPvxxh1FOlQuyWeiW4ID0QaclR2phpTI= +github.com/ctalledo/go-archive v0.2.1-0.20260717121834-2bc39549db4e/go.mod h1:M+VLaXxS7DSgrQxxhQKGCuT9Wki/pa1hzMgU3VLQeHQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go index 8df35ed4e080..8312ef48df02 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -499,10 +499,11 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea case tar.TypeLink: // Defence in depth: root.Link's containment is limited when // dest is a volume root. - if !filepath.IsLocal(hdr.Linkname) { + linkname := path.Clean(hdr.Linkname) + if linkname == "." || !filepath.IsLocal(linkname) { return breakoutError(fmt.Errorf("invalid hardlink target %q", hdr.Linkname)) } - if err := root.Link(filepath.FromSlash(hdr.Linkname), dstPath); err != nil { + if err := root.Link(filepath.FromSlash(linkname), dstPath); err != nil { return err } @@ -875,14 +876,23 @@ loop: continue } - // Strip a leading "/" so absolute entries stay root-relative, then - // Clean while keeping any ".." so the IsLocal check below rejects - // escapes instead of silently rewriting them. - hdr.Name = path.Clean(strings.TrimLeft(hdr.Name, "/")) - // Reject names that escape the extraction root (absolute or ".."). - if !filepath.IsLocal(hdr.Name) { + // Strip a leading "/" so absolute entries stay root-relative. + name := strings.TrimLeft(hdr.Name, "/") + if name == "" || name == "." { + continue + } + + // Normalize the path. Skip current-directory entries and reject + // entries that escape the extraction root. + name = path.Clean(name) + if name == "." { + continue + } + if !filepath.IsLocal(name) { return breakoutError(fmt.Errorf("invalid entry name %q", hdr.Name)) } + 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) diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index a70ae6469bd8..08b49905a4d5 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -87,16 +87,17 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { // 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 { - if hdr.Typeflag == tar.TypeLink { - if fi, err := root.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := root.Chmod(dstPath, hdrInfo.Mode()); err != nil { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := root.Chmod(dstPath, hdrInfo.Mode()); err != nil { - return err + switch hdr.Typeflag { + case tar.TypeSymlink: + return nil + + case tar.TypeLink: + if fi, err := root.Lstat(filepath.FromSlash(hdr.Linkname)); err == nil && fi.Mode()&os.ModeSymlink == 0 { + return root.Chmod(dstPath, hdrInfo.Mode()) } + return nil + + default: + return root.Chmod(dstPath, hdrInfo.Mode()) } - return nil } diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go index 3bdde513c36f..194fc718976a 100644 --- a/vendor/github.com/moby/go-archive/diff.go +++ b/vendor/github.com/moby/go-archive/diff.go @@ -56,14 +56,22 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, size += hdr.Size - // Strip a leading "/" so absolute entries stay root-relative, then - // Clean while keeping any ".." so the IsLocal check below rejects - // escapes instead of silently rewriting them. - hdr.Name = path.Clean(strings.TrimLeft(hdr.Name, "/")) - // Reject names that escape the extraction root (absolute or ".."). - if !filepath.IsLocal(hdr.Name) { + // Strip a leading "/" so absolute entries stay root-relative. + name := strings.TrimLeft(hdr.Name, "/") + if name == "" || name == "." { + continue + } + + // Normalize the path. Skip current-directory entries and reject + // entries that escape the extraction root. + name = path.Clean(name) + if name == "." { + continue + } + if !filepath.IsLocal(name) { return 0, breakoutError(fmt.Errorf("invalid entry name %q", hdr.Name)) } + hdr.Name = name // Skip entries whose name (or hardlink target) Windows cannot represent. if err := unrepresentableOnWindows(hdr); err != nil { @@ -132,23 +140,22 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, 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 p == absDir { return nil } - // unpackedPaths is keyed by the root-relative, forward-slash - // form of hdr.Name (the result of path.Join("/", ...) in - // the normalisation step above). filepath.WalkDir yields - // OS-native separators, so convert to slash form before - // looking up so the comparison works on Windows too. - rel := filepath.ToSlash(strings.TrimPrefix( - p, root.Name()+string(os.PathSeparator), - )) - if _, exists := unpackedPaths[rel]; !exists { - return os.RemoveAll(p) + 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 }) diff --git a/vendor/github.com/moby/go-archive/time_nonwindows.go b/vendor/github.com/moby/go-archive/time_nonwindows.go index 59592a44b86a..6d0da6c6a0da 100644 --- a/vendor/github.com/moby/go-archive/time_nonwindows.go +++ b/vendor/github.com/moby/go-archive/time_nonwindows.go @@ -54,9 +54,11 @@ func lchtimes(root *os.Root, name string, atime, mtime time.Time) error { timeToTimespec(atime), timeToTimespec(mtime), } - err = unix.UtimesNanoAt(int(parent.Fd()), base, utimes[:], unix.AT_SYMLINK_NOFOLLOW) - if err != nil && !errors.Is(err, unix.ENOSYS) { + 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 } diff --git a/vendor/modules.txt b/vendor/modules.txt index 123162d6d799..e29e819467a3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -162,7 +162,7 @@ github.com/mattn/go-runewidth # 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-0.20260716170833-91b7e546daf2 => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc +# github.com/moby/go-archive v0.2.1-0.20260716170833-91b7e546daf2 => github.com/ctalledo/go-archive v0.2.1-0.20260717121834-2bc39549db4e ## explicit; go 1.25 github.com/moby/go-archive github.com/moby/go-archive/compression @@ -565,4 +565,4 @@ gotest.tools/v3/skip # tags.cncf.io/container-device-interface v1.1.0 ## explicit; go 1.21 tags.cncf.io/container-device-interface/pkg/parser -# github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260716171756-df2ce6219bdc +# github.com/moby/go-archive => github.com/ctalledo/go-archive v0.2.1-0.20260717121834-2bc39549db4e