From 5f3d92cc9b207e7bca7ea0c813118f0294d32713 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:54:03 +0200 Subject: [PATCH 1/4] Revert "vendor: github.com/moby/go-archive v0.2.1" This reverts commit 53b2d03bb3b80c33bdbaa903a8ca12aac73c5a2b. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> (cherry picked from commit 9a09dbd6fc3555bbc0955a8fcc4239c57f928f3a) --- go.mod | 2 +- go.sum | 4 +- .../github.com/moby/go-archive/.golangci.yml | 12 - vendor/github.com/moby/go-archive/archive.go | 214 ++++++++---------- .../moby/go-archive/archive_linux.go | 98 ++++---- .../moby/go-archive/archive_unix.go | 35 +-- .../moby/go-archive/archive_windows.go | 10 +- vendor/github.com/moby/go-archive/changes.go | 18 +- .../moby/go-archive/changes_linux.go | 2 +- .../moby/go-archive/changes_other.go | 2 +- .../moby/go-archive/chrootarchive/archive.go | 3 + .../chrootarchive/archive_unix_nolinux.go | 4 +- .../go-archive/chrootarchive/diff_unix.go | 3 + .../go-archive/compression/compression.go | 4 +- vendor/github.com/moby/go-archive/copy.go | 27 ++- vendor/github.com/moby/go-archive/dev_unix.go | 2 +- vendor/github.com/moby/go-archive/diff.go | 27 ++- .../moby/go-archive/tarheader/tarheader.go | 2 +- .../go-archive/tarheader/tarheader_unix.go | 9 +- .../moby/go-archive/xattr_supported.go | 20 +- vendor/modules.txt | 4 +- 21 files changed, 222 insertions(+), 280 deletions(-) diff --git a/go.mod b/go.mod index 7c4d21bf3ecc..89f3be5b0a0d 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/in-toto/in-toto-golang v0.11.0 github.com/klauspost/compress v1.19.1 github.com/moby/docker-image-spec v1.3.1 - github.com/moby/go-archive v0.2.1 + github.com/moby/go-archive v0.2.0 github.com/moby/locker v1.0.1 github.com/moby/patternmatcher v0.6.1 github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4 diff --git a/go.sum b/go.sum index 01298dfc1faa..56fe52a74274 100644 --- a/go.sum +++ b/go.sum @@ -418,8 +418,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.2.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc= -github.com/moby/go-archive v0.2.1/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= diff --git a/vendor/github.com/moby/go-archive/.golangci.yml b/vendor/github.com/moby/go-archive/.golangci.yml index 761e36d9d944..21439e5c6951 100644 --- a/vendor/github.com/moby/go-archive/.golangci.yml +++ b/vendor/github.com/moby/go-archive/.golangci.yml @@ -9,7 +9,6 @@ issues: linters: enable: - errorlint - - gosec - unconvert - unparam exclusions: @@ -17,18 +16,7 @@ linters: presets: - comments - std-error-handling - rules: - # Ignore "G204: Subprocess launched with a potential tainted input or cmd arguments" - - path: '(.+)_test\.go' - linters: - - gosec - text: 'G204: Subprocess launched' settings: - gosec: - excludes: - - G301 # Expect directory permissions to be 0750 or less - - G304 # Potential file inclusion via variable - - G306 # Expect WriteFile permissions to be 0600 or less staticcheck: # Enable all options, with some exceptions. # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go index e59cfbb89d13..8dce2e6e2afa 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -46,18 +46,9 @@ type ( // TarOptions wraps the tar options. TarOptions struct { - // IncludeFiles lists archive-relative paths to include. - // Paths use POSIX ('/') separators. - IncludeFiles []string - - // ExcludePatterns lists archive-relative exclude patterns. - // Patterns use POSIX ('/') separators, matching patternmatcher semantics. - ExcludePatterns []string - Compression compression.Compression - // NoLchown disables applying ownership from the archive to extracted files - // and directories. Despite its historical name, it applies to all ownership - // changes, leaving extracted filesystem objects owned by the user performing - // the extraction. + IncludeFiles []string + ExcludePatterns []string + Compression compression.Compression NoLchown bool IDMap user.IdentityMapping ChownOpts *ChownOpts @@ -95,14 +86,10 @@ func NewDefaultArchiver() *Archiver { return &Archiver{Untar: Untar} } -// breakoutErr marks errors caused by archive breakout attempts. -// Unit tests use it to distinguish expected breakout failures from other -// errors. -type breakoutErr struct{ error } - -func breakoutError(err error) error { - return &breakoutErr{error: err} -} +// breakoutError is used to differentiate errors related to breaking out +// When testing archive breakout in the unit tests, this error is expected +// in order for the test to pass. +type breakoutError error const ( AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts @@ -111,17 +98,17 @@ const ( // IsArchivePath checks if the (possibly compressed) file at the given path // starts with a tar file header. -func IsArchivePath(filePath string) bool { - file, err := os.Open(filePath) +func IsArchivePath(path string) bool { + file, err := os.Open(path) if err != nil { return false } - defer func() { _ = file.Close() }() + defer file.Close() rdr, err := compression.DecompressStream(file) if err != nil { return false } - defer func() { _ = rdr.Close() }() + defer rdr.Close() r := tar.NewReader(rdr) _, err = r.Next() return err == nil @@ -142,10 +129,8 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi go func() { tarReader := tar.NewReader(inputTarStream) tarWriter := tar.NewWriter(pipeWriter) - defer func() { - _ = tarWriter.Close() - _ = inputTarStream.Close() - }() + defer inputTarStream.Close() + defer tarWriter.Close() modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { header, data, err := modifier(name, original, tarReader) @@ -179,7 +164,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi break } if err != nil { - _ = pipeWriter.CloseWithError(err) + pipeWriter.CloseWithError(err) return } @@ -187,11 +172,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 @@ -199,7 +184,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 } } @@ -207,12 +192,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 } @@ -233,7 +218,7 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro hdr.ModTime = hdr.ModTime.Truncate(time.Second) hdr.AccessTime = time.Time{} hdr.ChangeTime = time.Time{} - hdr.Mode = chmodTarEntry(hdr.Mode) + hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) hdr.Name = canonicalTarName(name, fi.IsDir()) return hdr, nil } @@ -242,7 +227,7 @@ const paxSchilyXattr = "SCHILY.xattr." // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem // to a tar header -func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error { +func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { const ( // Values based on linux/include/uapi/linux/capability.h xattrCapsSz2 = 20 @@ -250,7 +235,7 @@ func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error { vfsCapRevision2 = 2 vfsCapRevision3 = 3 ) - capability, _ := lgetxattr(filePath, "security.capability") + capability, _ := lgetxattr(path, "security.capability") if capability != nil { if capability[versionOffset] == vfsCapRevision3 { // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no @@ -307,10 +292,9 @@ func canonicalTarName(name string, isDir bool) string { return name } -// 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) +// 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) if err != nil { return err } @@ -318,17 +302,17 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { var link string if fi.Mode()&os.ModeSymlink != 0 { var err error - link, err = os.Readlink(srcPath) + link, err = os.Readlink(path) if err != nil { return err } } - hdr, err := FileInfoHeader(archivePath, fi, link) + hdr, err := FileInfoHeader(name, fi, link) if err != nil { return err } - if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil { + if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { return err } @@ -337,7 +321,7 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { if !fi.IsDir() && hasHardlinks(fi) { inode, err := getInodeFromStat(fi.Sys()) if err != nil { - return fmt.Errorf("unexpected file info for %q: %w", srcPath, err) + return err } // a link should have a name that it links too // and that linked name should be first in the tar archive @@ -346,7 +330,7 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { hdr.Linkname = oldpath hdr.Size = 0 // This Must be here for the writer math to add up! } else { - ta.SeenFiles[inode] = hdr.Name + ta.SeenFiles[inode] = name } } @@ -375,7 +359,7 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { } if ta.WhiteoutConverter != nil { - wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi) + wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) if err != nil { return err } @@ -386,12 +370,12 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { // hdr may have been updated to be a whiteout with returning // a whiteout header if wo != nil { - if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { - return fmt.Errorf("tar: cannot use whiteout for non-empty file %q", hdr.Name) - } if err := ta.TarWriter.WriteHeader(hdr); err != nil { return err } + if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { + return fmt.Errorf("tar: cannot use whiteout for non-empty file") + } hdr = wo } } @@ -403,13 +387,13 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath 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(srcPath) + file, err := sequential.Open(path) if err != nil { return err } err = copyWithBuffer(ta.TarWriter, file) - _ = file.Close() + file.Close() if err != nil { return err } @@ -418,7 +402,7 @@ func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { return nil } -func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { +func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { var ( Lchown = true inUserns, bestEffortXattrs bool @@ -442,8 +426,8 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader 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 { + if fi, err := os.Lstat(path); err != nil || !fi.IsDir() { + if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { return err } } @@ -451,7 +435,7 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader 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()) + file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) if err != nil { return err } @@ -463,20 +447,20 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader case tar.TypeBlock, tar.TypeChar: if inUserns { // cannot create devices in a userns - log.G(context.TODO()).WithFields(log.Fields{"path": dstPath, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") + log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") return nil } // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { + if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { return err } case tar.TypeFifo: // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { + if err := handleTarTypeBlockCharFifo(hdr, path); 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") + log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") return nil } return err @@ -484,26 +468,26 @@ 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) + targetPath := 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)) + if !strings.HasPrefix(targetPath, extractDir) { + return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) } - if err := os.Link(linkTarget, dstPath); err != nil { + if err := os.Link(targetPath, path); 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. + targetPath := filepath.Join(filepath.Dir(path), 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)) + return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) } - if err := os.Symlink(hdr.Linkname, dstPath); err != nil { + if err := os.Symlink(hdr.Linkname, path); err != nil { return err } @@ -520,12 +504,12 @@ 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 := os.Lchown(path, 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", dstPath, hdr.Uid, hdr.Gid, msg, err) + return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err) } } @@ -535,7 +519,7 @@ func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader if !ok { continue } - if err := lsetxattr(dstPath, xattr, []byte(value), 0); err != nil { + if err := lsetxattr(path, xattr, []byte(value), 0); err != nil { if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) { // EPERM occurs if modifying xattrs is not allowed. This can // happen when running in userns with restrictions (ChromeOS). @@ -554,43 +538,39 @@ 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(hdr, path, hdrInfo); err != nil { return err } aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) mTime := boundTime(hdr.ModTime) - switch hdr.Typeflag { - case tar.TypeSymlink: - // Apply timestamps to the symlink itself (AT_SYMLINK_NOFOLLOW). - if err := lchtimes(dstPath, aTime, mTime); err != nil { - return err - } - case tar.TypeLink: - // Follow the hardlink only when its target is not itself a symlink. - fi, err := os.Lstat(hdr.Linkname) - if err == nil && fi.Mode()&os.ModeSymlink == 0 { - if err := chtimes(dstPath, aTime, mTime); err != nil { + // 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 { return err } } - default: - // All other file types follow symlinks. - if err := chtimes(dstPath, aTime, mTime); err != nil { + } else if hdr.Typeflag != tar.TypeSymlink { + if err := chtimes(path, aTime, mTime); err != nil { + return err + } + } else { + if err := lchtimes(path, aTime, mTime); err != nil { return err } } return nil } -// Tar creates an archive from the directory at `srcPath`, and returns it as a +// Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. -func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) { - return TarWithOptions(srcPath, &TarOptions{Compression: comp}) +func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { + return TarWithOptions(path, &TarOptions{Compression: comp}) } -// TarWithOptions creates an archive from the directory at `srcPath`, only including files whose relative +// TarWithOptions creates an archive from the directory at `path`, 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) @@ -825,9 +805,6 @@ func (t *Tarballer) Do() { // Unpack unpacks the decompressedArchive to dest with options. func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { - if options == nil { - options = &TarOptions{} - } tr := tar.NewReader(decompressedArchive) var dirs []*tar.Header @@ -869,8 +846,8 @@ loop: } // #nosec G305 -- The joined path is checked for path traversal. - dstPath := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, dstPath) + path := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, path) if err != nil { return err } @@ -878,21 +855,21 @@ loop: return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } - // If dstPath exists we almost always just want to remove and replace it. + // If path 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 := os.Lstat(path); 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", path, 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", path, dest) } if fi.IsDir() && hdr.Name == "." { @@ -900,7 +877,7 @@ loop: } if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(dstPath); err != nil { + if err := os.RemoveAll(path); err != nil { return err } } @@ -911,7 +888,7 @@ loop: } if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath) + writeFile, err := whiteoutConverter.ConvertRead(hdr, path) if err != nil { return err } @@ -920,7 +897,7 @@ loop: } } - if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil { + if err := createTarFile(path, dest, hdr, tr, options); err != nil { return err } @@ -933,8 +910,9 @@ loop: 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 { + path := filepath.Join(dest, hdr.Name) + + if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { return err } } @@ -945,15 +923,16 @@ 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 { - // For non-directory entries, ensure that the parent directory exists. - if hdr.Typeflag != tar.TypeDir { + // Not the root directory, ensure that the parent directory exists + if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - if options.NoLchown { - return os.MkdirAll(parentPath, ImpliedDirectoryMode) - } // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche // usage that reduces the portability of an image. @@ -995,6 +974,9 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp if options == nil { options = &TarOptions{} } + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} + } r := tarArchive if decompress { @@ -1002,7 +984,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp if err != nil { return err } - defer func() { _ = decompressedArchive.Close() }() + defer decompressedArchive.Close() r = decompressedArchive } @@ -1016,7 +998,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error { if err != nil { return err } - defer func() { _ = archive.Close() }() + defer archive.Close() return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, }) @@ -1028,7 +1010,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error { if err != nil { return err } - defer func() { _ = archive.Close() }() + defer archive.Close() return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, }) @@ -1088,13 +1070,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { defer close(errC) errC <- func() error { - defer func() { _ = w.Close() }() + defer w.Close() srcF, err := os.Open(src) if err != nil { return err } - defer func() { _ = srcF.Close() }() + defer srcF.Close() hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") if err != nil { @@ -1105,14 +1087,14 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { hdr.AccessTime = time.Time{} hdr.ChangeTime = time.Time{} hdr.Name = filepath.Base(dst) - hdr.Mode = chmodTarEntry(hdr.Mode) + hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) if err := remapIDs(archiver.IDMapping, hdr); err != nil { return err } tw := tar.NewWriter(w) - defer func() { _ = tw.Close() }() + defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } @@ -1130,7 +1112,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 bddef788cca5..7b6c3e02b643 100644 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ b/vendor/github.com/moby/go-archive/archive_linux.go @@ -4,7 +4,6 @@ import ( "archive/tar" "fmt" "os" - "path" "path/filepath" "strings" @@ -14,43 +13,36 @@ import ( func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { if format == OverlayWhiteoutFormat { - return newOverlayWhiteoutConverter() + return overlayWhiteoutConverter{} } return nil } -type overlayWhiteoutConverter struct { - opaqueXattr string -} - -func newOverlayWhiteoutConverter() overlayWhiteoutConverter { - opaqueXattr := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattr = "user.overlay.opaque" - } - return overlayWhiteoutConverter{ - opaqueXattr: opaqueXattr, - } -} +type overlayWhiteoutConverter struct{} -func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ error) { +func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path 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 := path.Split(hdr.Name) - hdr.Name = path.Join(dir, WhiteoutPrefix+filename) + dir, filename := filepath.Split(hdr.Name) + hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) hdr.Mode = 0o600 hdr.Typeflag = tar.TypeReg hdr.Size = 0 } - if !fi.IsDir() { + if fi.Mode()&os.ModeDir == 0 { // FIXME(thaJeztah): return a sentinel error instead of nil, nil return nil, nil } + opaqueXattrName := "trusted.overlay.opaque" + if userns.RunningInUserNS() { + opaqueXattrName = "user.overlay.opaque" + } + // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(filePath, c.opaqueXattr) + opaque, err := lgetxattr(path, opaqueXattrName) if err != nil { return nil, err } @@ -58,14 +50,14 @@ func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, // FIXME(thaJeztah): return a sentinel error instead of nil, nil return nil, nil } - delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr) + delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) // create a header for the whiteout file // it should inherit some properties from the parent, but be a regular file return &tar.Header{ Typeflag: tar.TypeReg, Mode: hdr.Mode & int64(os.ModePerm), - Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. + Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. Size: 0, Uid: hdr.Uid, Uname: hdr.Uname, @@ -76,54 +68,40 @@ func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, }, nil } -func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) { - base := filepath.Base(filePath) - dir := filepath.Dir(filePath) - - switch base { - case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..": - return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name) +func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { + base := filepath.Base(path) + dir := filepath.Dir(path) - case WhiteoutOpaqueDir: - // If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay. - if err := unix.Setxattr(dir, c.opaqueXattr, []byte{'y'}, 0); err != nil { - return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, c.opaqueXattr, err) + // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay + if base == WhiteoutOpaqueDir { + opaqueXattrName := "trusted.overlay.opaque" + if userns.RunningInUserNS() { + opaqueXattrName = "user.overlay.opaque" } - // Don't write the whiteout file itself. - return false, nil - default: - originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix) - if !ok { - // Regular file. - return true, nil + err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0) + if err != nil { + return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err) } - // If a file was deleted, and we are using overlay, we need to create a character device. + // don't write the file itself + return false, err + } + + // if a file was deleted and we are using overlay, we need to create a character device + if strings.HasPrefix(base, WhiteoutPrefix) { + originalBase := base[len(WhiteoutPrefix):] originalPath := filepath.Join(dir, originalBase) + if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err) } - - // Header IDs have already been remapped. Optimize the common non-remapped - // root-owned (0:0) case by assuming the created whiteout has the expected - // ownership, rather than comparing against the effective UID/GID or stat'ing - // the created node to verify it. - if hdr.Uid != 0 || hdr.Gid != 0 { - // TODO(thaJeztah): Revisit whether whiteout ownership needs to be preserved. - // - // This was added in the original overlay whiteout implementation: - // https://github.com/moby/moby/pull/18560 / https://github.com/moby/moby/pull/22126 - // - // OverlayFS documents whiteouts in terms of a character device with device - // number 0:0, not ownership: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories - // - // If ownership is not required, this Lchown can be removed to avoid the remaining TOCTOU window. - if err := os.Lchown(originalPath, hdr.Uid, hdr.Gid); err != nil { - return false, err - } + if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { + return false, err } - // Don't write the whiteout file itself. + // don't write the file itself return false, nil } + + return true, nil } diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index b8b4cadab26f..3a9f5b0b5863 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -5,8 +5,6 @@ package archive import ( "archive/tar" "errors" - "fmt" - "math" "os" "path/filepath" "strings" @@ -15,8 +13,6 @@ 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,19 +29,20 @@ func getWalkRoot(srcPath string, include string) string { // chmodTarEntry is used to adjust the file permissions used in tar header based // on the platform the archival is done. -func chmodTarEntry(mode int64) int64 { - return mode // noop for unix as golang APIs provide perm bits correctly +func chmodTarEntry(perm os.FileMode) os.FileMode { + return perm // noop for unix as golang APIs provide perm bits correctly } -func getInodeFromStat(stat any) (uint64, error) { +func getInodeFromStat(stat interface{}) (uint64, error) { s, ok := stat.(*syscall.Stat_t) if !ok { - return 0, fmt.Errorf("unexpected stat type %T", stat) + // FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897 + return 0, nil } return s.Ino, nil } -func getFileUIDGID(stat any) (int, int, error) { +func getFileUIDGID(stat interface{}) (int, int, error) { s, ok := stat.(*syscall.Stat_t) if !ok { @@ -59,7 +56,7 @@ func getFileUIDGID(stat any) (int, int, error) { // // Creating device nodes is not supported when running in a user namespace, // produces a [syscall.EPERM] in most cases. -func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { +func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { mode := uint32(hdr.Mode & 0o7777) switch hdr.Typeflag { case tar.TypeBlock: @@ -70,28 +67,18 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { mode |= unix.S_IFIFO } - // 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))) + return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) } -func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error { +func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { + if err := os.Chmod(path, hdrInfo.Mode()); err != nil { return err } } } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { + if err := os.Chmod(path, 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 ee8dbd6830ca..0e3e316afc2f 100644 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ b/vendor/github.com/moby/go-archive/archive_windows.go @@ -33,15 +33,15 @@ func getWalkRoot(srcPath string, include string) string { // chmodTarEntry is used to adjust the file permissions used in tar header based // on the platform the archival is done. -func chmodTarEntry(mode int64) int64 { +func chmodTarEntry(perm os.FileMode) os.FileMode { // Remove group- and world-writable bits. - mode &= 0o755 + perm &= 0o755 // Add the x bit: make everything +x on Windows - return mode | 0o111 + return perm | 0o111 } -func getInodeFromStat(stat any) (uint64, error) { +func getInodeFromStat(stat interface{}) (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 any) (int, int, error) { +func getFileUIDGID(stat interface{}) (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 c5d647c030d2..02a0372c6f99 100644 --- a/vendor/github.com/moby/go-archive/changes.go +++ b/vendor/github.com/moby/go-archive/changes.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/fs" - "maps" "os" "path/filepath" "sort" @@ -218,8 +217,8 @@ func (info *FileInfo) LookUp(path string) *FileInfo { return info } - pathElements := strings.SplitSeq(path, string(os.PathSeparator)) - for elem := range pathElements { + pathElements := strings.Split(path, string(os.PathSeparator)) + for _, elem := range pathElements { if elem != "" { child := parent.children[elem] if child == nil { @@ -257,7 +256,9 @@ 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() { - maps.Copy(oldChildren, oldInfo.children) + for k, v := range oldInfo.children { + oldChildren[k] = v + } } for name, newChild := range info.children { @@ -400,7 +401,7 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) timestamp := time.Now() hdr := &tar.Header{ - Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"), + Name: whiteOut[1:], Size: 0, ModTime: timestamp, AccessTime: timestamp, @@ -410,10 +411,9 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err) } } else { - 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) + 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) } } } diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go index 39ec7475f4ea..8289fe17d906 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 := range n { + for i := 0; i < len(n); i++ { 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 7eb195d500ec..a8a3a5a6faa8 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 range 2 { + for i := 0; i < 2; i++ { if err := <-errs; err != nil { return nil, nil, err } diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive.go b/vendor/github.com/moby/go-archive/chrootarchive/archive.go index 1e79837db8e3..26da8e2924ba 100644 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive.go +++ b/vendor/github.com/moby/go-archive/chrootarchive/archive.go @@ -59,6 +59,9 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions if options == nil { options = &archive.TarOptions{} } + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} + } // If dest is inside a root then directory is created within chroot by extractor. // This case is only currently used by cp. diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go index 6341860bcb06..f5b02ea55068 100644 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go +++ b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go @@ -119,9 +119,9 @@ func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, er _, _ = io.Copy(w, stdout) // Cleanup once stdout pipe is closed. if err = cmd.Wait(); err != nil { - _ = r.CloseWithError(fmt.Errorf("%s: %w", stderr.String(), err)) + r.CloseWithError(fmt.Errorf("%s: %w", stderr.String(), err)) } else { - _ = r.Close() + r.Close() } }() diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go b/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go index de6f052eb6f9..0e95bcd95bc3 100644 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go +++ b/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go @@ -31,6 +31,9 @@ func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions if userns.RunningInUserNS() { options.InUserNS = true } + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} + } dest = filepath.Clean(dest) return doUnpackLayer(dest, layer, options) } diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go index 824267924d8c..e298cefb3249 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() any { return bufio.NewReaderSize(nil, 32*1024) }, + New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, } type bufferedReader struct { @@ -217,7 +217,7 @@ func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { log.G(ctx).Debugf("Using %s to decompress", unpigzPath) - return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) // #nosec G204 -- Subprocess launched with variable + return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) } // cmdStream executes a command, and returns its stdout as a stream. diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go index b4ee74ef47c9..77d038c42310 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() any { s := make([]byte, 32*1024); return &s }, + New: func() interface{} { s := make([]byte, 32*1024); return &s }, } func copyWithBuffer(dst io.Writer, src io.Reader) error { @@ -319,10 +319,7 @@ 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 { - oldBase = filepath.ToSlash(oldBase) - newBase = filepath.ToSlash(newBase) - - if oldBase == "/" { + if oldBase == string(os.PathSeparator) { // 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. @@ -339,12 +336,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 } @@ -362,7 +359,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 } @@ -377,7 +374,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 } } @@ -411,7 +408,7 @@ func CopyResource(srcPath, dstPath string, followLink bool) error { if err != nil { return err } - defer func() { _ = content.Close() }() + defer content.Close() return CopyTo(content, srcInfo, dstPath) } @@ -430,12 +427,14 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { if err != nil { return err } - defer func() { _ = copyArchive.Close() }() + defer copyArchive.Close() - return Untar(copyArchive, dstDir, &TarOptions{ + options := &TarOptions{ NoLchown: true, NoOverwriteDirNonDir: true, - }) + } + + return Untar(copyArchive, dstDir, options) } // ResolveHostSourcePath decides real path need to be copied with parameters such as diff --git a/vendor/github.com/moby/go-archive/dev_unix.go b/vendor/github.com/moby/go-archive/dev_unix.go index 2f9833b85b2c..dffc596f93f7 100644 --- a/vendor/github.com/moby/go-archive/dev_unix.go +++ b/vendor/github.com/moby/go-archive/dev_unix.go @@ -5,5 +5,5 @@ package archive import "golang.org/x/sys/unix" func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API. + return unix.Mknod(path, mode, int(dev)) } diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go index b2fdf770a651..96db972d1383 100644 --- a/vendor/github.com/moby/go-archive/diff.go +++ b/vendor/github.com/moby/go-archive/diff.go @@ -28,6 +28,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if options == nil { options = &TarOptions{} } + if options.ExcludePatterns == nil { + options.ExcludePatterns = []string{} + } aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) @@ -99,8 +102,8 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, } } // #nosec G305 -- The joined path is guarded against path traversal. - dstPath := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, dstPath) + path := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, path) if err != nil { return 0, err } @@ -109,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(dstPath) + base := filepath.Base(path) if strings.HasPrefix(base, WhiteoutPrefix) { - dir := filepath.Dir(dstPath) + dir := filepath.Dir(path) if base == WhiteoutOpaqueDir { _, err := os.Lstat(dir) if err != nil { @@ -129,7 +132,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, return nil } if _, exists := unpackedPaths[path]; !exists { - return os.RemoveAll(path) // #nosec G122 -- FIXME: consider root-scoped APIs (e.g. os.Root) to prevent symlink TOCTOU traversal + return os.RemoveAll(path) } return nil }) @@ -144,13 +147,13 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, } } } else { - // If dstPath exists we almost always just want to remove and replace it. + // If path 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 := os.Lstat(path); err == nil { if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(dstPath); err != nil { + if err := os.RemoveAll(path); err != nil { return 0, err } } @@ -179,7 +182,7 @@ 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(path, dest, srcHdr, srcData, options); err != nil { return 0, err } @@ -188,14 +191,14 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } - unpackedPaths[dstPath] = struct{}{} + unpackedPaths[path] = 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 { + path := filepath.Join(dest, hdr.Name) + if err := chtimes(path, 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 78ab55cd410f..03732a4f844b 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() any { +func (fi nosysFileInfo) Sys() interface{} { // 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/tarheader/tarheader_unix.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go index bd12d46a8a67..9c3311c63abc 100644 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go +++ b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go @@ -36,11 +36,10 @@ func sysStat(fi os.FileInfo, hdr *tar.Header) error { hdr.Uid = int(s.Uid) hdr.Gid = int(s.Gid) - if s.Mode&unix.S_IFBLK != 0 || s.Mode&unix.S_IFCHR != 0 { - // #nosec G115 -- Rdev type varies by platform. - rdev := uint64(s.Rdev) //nolint:unconvert // Rdev type varies by platform. - hdr.Devmajor = int64(unix.Major(rdev)) - hdr.Devminor = int64(unix.Minor(rdev)) + if s.Mode&unix.S_IFBLK != 0 || + s.Mode&unix.S_IFCHR != 0 { + hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert + hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert } return nil diff --git a/vendor/github.com/moby/go-archive/xattr_supported.go b/vendor/github.com/moby/go-archive/xattr_supported.go index 8478fe1446da..652a1f0f349d 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(filePath string, attr string) ([]byte, error) { +func lgetxattr(path string, attr string) ([]byte, error) { // Start with a 128 length byte array dest := make([]byte, 128) - sz, err := unix.Lgetxattr(filePath, attr, dest) + sz, err := unix.Lgetxattr(path, attr, dest) for errors.Is(err, unix.ERANGE) { // Buffer too small, use zero-sized buffer to get the actual size - sz, err = unix.Lgetxattr(filePath, attr, []byte{}) + sz, err = unix.Lgetxattr(path, attr, []byte{}) if err != nil { - return nil, wrapPathError("lgetxattr", filePath, attr, err) + return nil, wrapPathError("lgetxattr", path, attr, err) } dest = make([]byte, sz) - sz, err = unix.Lgetxattr(filePath, attr, dest) + sz, err = unix.Lgetxattr(path, attr, dest) } if err != nil { if errors.Is(err, noattr) { return nil, nil } - return nil, wrapPathError("lgetxattr", filePath, attr, err) + return nil, wrapPathError("lgetxattr", path, attr, err) } return dest[:sz], nil @@ -40,13 +40,13 @@ func lgetxattr(filePath 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(filePath string, attr string, data []byte, flags int) error { - return wrapPathError("lsetxattr", filePath, attr, unix.Lsetxattr(filePath, attr, data, flags)) +func lsetxattr(path string, attr string, data []byte, flags int) error { + return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags)) } -func wrapPathError(op, filePath, attr string, err error) error { +func wrapPathError(op, path, attr string, err error) error { if err == nil { return nil } - return &fs.PathError{Op: op, Path: filePath, Err: fmt.Errorf("xattr %q: %w", attr, err)} + return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)} } diff --git a/vendor/modules.txt b/vendor/modules.txt index 1a6e108dedb5..199b09c2959f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -795,8 +795,8 @@ github.com/kylelemons/godebug/pretty # 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 -## explicit; go 1.25 +# github.com/moby/go-archive v0.2.0 +## explicit; go 1.23.0 github.com/moby/go-archive github.com/moby/go-archive/chrootarchive github.com/moby/go-archive/compression From f3b57e4baeffab204070be8dbd0c85caed692edc Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:56:09 +0200 Subject: [PATCH 2/4] dockerfile: regression test for ADD archives with implied parents Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> (cherry picked from commit ec7e7bff5fce8b5c305bd70ae75d7fa3ff3e1e79) --- frontend/dockerfile/dockerfile_add_test.go | 59 ++++++++++++++++++++++ frontend/dockerfile/dockerfile_test.go | 1 + 2 files changed, 60 insertions(+) diff --git a/frontend/dockerfile/dockerfile_add_test.go b/frontend/dockerfile/dockerfile_add_test.go index 20ceb469b21c..6dc3ac62effd 100644 --- a/frontend/dockerfile/dockerfile_add_test.go +++ b/frontend/dockerfile/dockerfile_add_test.go @@ -323,6 +323,65 @@ ADD %s /newname.tar.gz require.Equal(t, buf2.Bytes(), dt) } +// https://github.com/moby/moby/issues/53257 +func testDockerfileAddArchiveWithImpliedParentDir(t *testing.T, sb integration.Sandbox) { + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + err := tw.WriteHeader(&tar.Header{ + Name: "etc/dnf/", + Typeflag: tar.TypeDir, + Mode: 0755, + }) + require.NoError(t, err) + expectedContent := []byte("content0") + err = tw.WriteHeader(&tar.Header{ + Name: "etc/dnf/dnf.conf", + Typeflag: tar.TypeReg, + Size: int64(len(expectedContent)), + Mode: 0644, + }) + require.NoError(t, err) + _, err = tw.Write(expectedContent) + require.NoError(t, err) + err = tw.Close() + require.NoError(t, err) + + gzBuf := bytes.NewBuffer(nil) + gz := gzip.NewWriter(gzBuf) + _, err = gz.Write(buf.Bytes()) + require.NoError(t, err) + err = gz.Close() + require.NoError(t, err) + + baseImage := integration.UnixOrWindows("scratch", "nanoserver") + + dockerfile := fmt.Appendf(nil, ` +FROM %s +ADD t.tar.gz / +`, baseImage) + + dir := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("t.tar.gz", gzBuf.Bytes(), 0600), + ) + + args, trace := f.DFCmdArgs(dir.Name, dir.Name) + defer os.RemoveAll(trace) + + destDir := t.TempDir() + + cmd := sb.Cmd(args + fmt.Sprintf(" --output type=local,dest=%s", destDir)) + require.NoError(t, cmd.Run()) + + dt, err := os.ReadFile(filepath.Join(destDir, "etc/dnf/dnf.conf")) + require.NoError(t, err) + require.Equal(t, expectedContent, dt) +} + func testDockerfileAddChownArchive(t *testing.T, sb integration.Sandbox) { integration.SkipOnPlatform(t, "windows", "ADD --chown tests Unix UID/GID ownership which is not applicable to Windows file permissions") f := getFrontend(t, sb) diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 7687ac069bdc..9381973ec2c0 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -36,6 +36,7 @@ var allTests = integration.TestFuncs( // dockerfile_add_test.go testDockerfileADDFromURL, testDockerfileAddArchive, + testDockerfileAddArchiveWithImpliedParentDir, testDockerfileAddChownArchive, testDockerfileAddArchiveWildcard, testDockerfileAddChownExpand, From c7dc8c842f1b7c8919538b15d160a384bc0a0479 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:08:41 +0200 Subject: [PATCH 3/4] dockerfile: regression test for ADD archives through absolute symlinks Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> (cherry picked from commit 0f87fc84e7db512732148775a873564427bd8fb7) --- frontend/dockerfile/dockerfile_add_test.go | 76 ++++++++++++++++++++++ frontend/dockerfile/dockerfile_test.go | 1 + 2 files changed, 77 insertions(+) diff --git a/frontend/dockerfile/dockerfile_add_test.go b/frontend/dockerfile/dockerfile_add_test.go index 6dc3ac62effd..9059640ea5c3 100644 --- a/frontend/dockerfile/dockerfile_add_test.go +++ b/frontend/dockerfile/dockerfile_add_test.go @@ -382,6 +382,82 @@ ADD t.tar.gz / require.Equal(t, expectedContent, dt) } +// https://github.com/moby/moby/issues/53258 +func testDockerfileAddArchiveThroughAbsoluteSymlink(t *testing.T, sb integration.Sandbox) { + integration.SkipOnPlatform(t, "windows", "absolute /run symlink traversal is a Unix-specific archive extraction case") + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + writeDir := func(tw *tar.Writer, name string) { + err := tw.WriteHeader(&tar.Header{ + Name: name, + Typeflag: tar.TypeDir, + Mode: 0755, + }) + require.NoError(t, err) + } + + rootfs := bytes.NewBuffer(nil) + tw := tar.NewWriter(rootfs) + writeDir(tw, "run/") + writeDir(tw, "var/") + err := tw.WriteHeader(&tar.Header{ + Name: "var/run", + Typeflag: tar.TypeSymlink, + Linkname: "/run", + Mode: 0777, + }) + require.NoError(t, err) + err = tw.Close() + require.NoError(t, err) + + payload := bytes.NewBuffer(nil) + tw = tar.NewWriter(payload) + // Keep parent directories explicit so this isolates extraction through the + // absolute symlink from implied parent directory handling. + writeDir(tw, "var/run/act/") + writeDir(tw, "var/run/act/actions/") + writeDir(tw, "var/run/act/actions/broad/") + writeDir(tw, "var/run/act/actions/broad/.git/") + expectedContent := []byte("ref: refs/heads/main\n") + err = tw.WriteHeader(&tar.Header{ + Name: "var/run/act/actions/broad/.git/HEAD", + Typeflag: tar.TypeReg, + Size: int64(len(expectedContent)), + Mode: 0644, + }) + require.NoError(t, err) + _, err = tw.Write(expectedContent) + require.NoError(t, err) + err = tw.Close() + require.NoError(t, err) + + dockerfile := []byte(` +FROM scratch +ADD rootfs.tar / +ADD payload.tar / +`) + + dir := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("rootfs.tar", rootfs.Bytes(), 0600), + fstest.CreateFile("payload.tar", payload.Bytes(), 0600), + ) + + args, trace := f.DFCmdArgs(dir.Name, dir.Name) + defer os.RemoveAll(trace) + + destDir := t.TempDir() + + cmd := sb.Cmd(args + fmt.Sprintf(" --output type=local,dest=%s", destDir)) + require.NoError(t, cmd.Run()) + + dt, err := os.ReadFile(filepath.Join(destDir, "run/act/actions/broad/.git/HEAD")) + require.NoError(t, err) + require.Equal(t, expectedContent, dt) +} + func testDockerfileAddChownArchive(t *testing.T, sb integration.Sandbox) { integration.SkipOnPlatform(t, "windows", "ADD --chown tests Unix UID/GID ownership which is not applicable to Windows file permissions") f := getFrontend(t, sb) diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 9381973ec2c0..a32680e94220 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -37,6 +37,7 @@ var allTests = integration.TestFuncs( testDockerfileADDFromURL, testDockerfileAddArchive, testDockerfileAddArchiveWithImpliedParentDir, + testDockerfileAddArchiveThroughAbsoluteSymlink, testDockerfileAddChownArchive, testDockerfileAddArchiveWildcard, testDockerfileAddChownExpand, From 640eb23333e717a25c561fea804da8f514b671d9 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:46:48 +0200 Subject: [PATCH 4/4] dockerfile: test ADD hardlinks through absolute symlinks Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> (cherry picked from commit 22ea4efb43c3c91651dab7f44d1599c4c42b9412) --- frontend/dockerfile/dockerfile_add_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/dockerfile/dockerfile_add_test.go b/frontend/dockerfile/dockerfile_add_test.go index 9059640ea5c3..ef18a9ddf74f 100644 --- a/frontend/dockerfile/dockerfile_add_test.go +++ b/frontend/dockerfile/dockerfile_add_test.go @@ -429,6 +429,13 @@ func testDockerfileAddArchiveThroughAbsoluteSymlink(t *testing.T, sb integration require.NoError(t, err) _, err = tw.Write(expectedContent) require.NoError(t, err) + err = tw.WriteHeader(&tar.Header{ + Name: "var/run/act/actions/broad/.git/HEAD.link", + Typeflag: tar.TypeLink, + Linkname: "var/run/act/actions/broad/.git/HEAD", + Mode: 0644, + }) + require.NoError(t, err) err = tw.Close() require.NoError(t, err) @@ -456,6 +463,17 @@ ADD payload.tar / dt, err := os.ReadFile(filepath.Join(destDir, "run/act/actions/broad/.git/HEAD")) require.NoError(t, err) require.Equal(t, expectedContent, dt) + + linkPath := filepath.Join(destDir, "run/act/actions/broad/.git/HEAD.link") + dt, err = os.ReadFile(linkPath) + require.NoError(t, err) + require.Equal(t, expectedContent, dt) + + fileInfo, err := os.Stat(filepath.Join(destDir, "run/act/actions/broad/.git/HEAD")) + require.NoError(t, err) + linkInfo, err := os.Stat(linkPath) + require.NoError(t, err) + require.True(t, os.SameFile(fileInfo, linkInfo)) } func testDockerfileAddChownArchive(t *testing.T, sb integration.Sandbox) {