vendor: update buildkit to v0.32.0-rc2

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2026-07-27 18:14:20 +02:00
parent 44c2e31f8d
commit 4ca627539d
18 changed files with 282 additions and 218 deletions
+2 -2
View File
@@ -30,8 +30,8 @@ require (
github.com/hashicorp/hcl/v2 v2.24.0 github.com/hashicorp/hcl/v2 v2.24.0
github.com/in-toto/in-toto-golang v0.11.0 github.com/in-toto/in-toto-golang v0.11.0
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.32.0-rc1 github.com/moby/buildkit v0.32.0-rc2
github.com/moby/go-archive v0.2.0 github.com/moby/go-archive v0.2.1
github.com/moby/moby/api v1.55.0 github.com/moby/moby/api v1.55.0
github.com/moby/moby/client v0.5.0 github.com/moby/moby/client v0.5.0
github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4 github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4
+4 -4
View File
@@ -395,12 +395,12 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.32.0-rc1 h1:gYtsqXOSA43i9zTf2w+0KzTxpZYao0ZYr/AaGx0RJhs= github.com/moby/buildkit v0.32.0-rc2 h1:2XiJmnPSZyJ0akoKG2ShWAFbWRT5DKvyAI2+Johq9Ac=
github.com/moby/buildkit v0.32.0-rc1/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8= github.com/moby/buildkit v0.32.0-rc2/go.mod h1:Y10FBWvqxl/Wmhdzjee1Y2wQfjifTiwxENIUdaVNdME=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 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.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/go-archive v0.2.1/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= 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/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
+12
View File
@@ -9,6 +9,7 @@ issues:
linters: linters:
enable: enable:
- errorlint - errorlint
- gosec
- unconvert - unconvert
- unparam - unparam
exclusions: exclusions:
@@ -16,7 +17,18 @@ linters:
presets: presets:
- comments - comments
- std-error-handling - 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: 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: staticcheck:
# Enable all options, with some exceptions. # Enable all options, with some exceptions.
# For defaults, see https://golangci-lint.run/usage/linters/#staticcheck # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck
+113 -95
View File
@@ -46,9 +46,18 @@ type (
// TarOptions wraps the tar options. // TarOptions wraps the tar options.
TarOptions struct { TarOptions struct {
// IncludeFiles lists archive-relative paths to include.
// Paths use POSIX ('/') separators.
IncludeFiles []string IncludeFiles []string
// ExcludePatterns lists archive-relative exclude patterns.
// Patterns use POSIX ('/') separators, matching patternmatcher semantics.
ExcludePatterns []string ExcludePatterns []string
Compression compression.Compression 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.
NoLchown bool NoLchown bool
IDMap user.IdentityMapping IDMap user.IdentityMapping
ChownOpts *ChownOpts ChownOpts *ChownOpts
@@ -86,10 +95,14 @@ func NewDefaultArchiver() *Archiver {
return &Archiver{Untar: Untar} return &Archiver{Untar: Untar}
} }
// breakoutError is used to differentiate errors related to breaking out // breakoutErr marks errors caused by archive breakout attempts.
// When testing archive breakout in the unit tests, this error is expected // Unit tests use it to distinguish expected breakout failures from other
// in order for the test to pass. // errors.
type breakoutError error type breakoutErr struct{ error }
func breakoutError(err error) error {
return &breakoutErr{error: err}
}
const ( const (
AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts
@@ -98,17 +111,17 @@ const (
// IsArchivePath checks if the (possibly compressed) file at the given path // IsArchivePath checks if the (possibly compressed) file at the given path
// starts with a tar file header. // starts with a tar file header.
func IsArchivePath(path string) bool { func IsArchivePath(filePath string) bool {
file, err := os.Open(path) file, err := os.Open(filePath)
if err != nil { if err != nil {
return false return false
} }
defer file.Close() defer func() { _ = file.Close() }()
rdr, err := compression.DecompressStream(file) rdr, err := compression.DecompressStream(file)
if err != nil { if err != nil {
return false return false
} }
defer rdr.Close() defer func() { _ = rdr.Close() }()
r := tar.NewReader(rdr) r := tar.NewReader(rdr)
_, err = r.Next() _, err = r.Next()
return err == nil return err == nil
@@ -129,8 +142,10 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
go func() { go func() {
tarReader := tar.NewReader(inputTarStream) tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter) tarWriter := tar.NewWriter(pipeWriter)
defer inputTarStream.Close() defer func() {
defer tarWriter.Close() _ = tarWriter.Close()
_ = inputTarStream.Close()
}()
modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error {
header, data, err := modifier(name, original, tarReader) header, data, err := modifier(name, original, tarReader)
@@ -164,7 +179,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
break break
} }
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) _ = pipeWriter.CloseWithError(err)
return return
} }
@@ -172,11 +187,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
if !ok { if !ok {
// No modifiers for this file, copy the header and data // No modifiers for this file, copy the header and data
if err := tarWriter.WriteHeader(originalHeader); err != nil { if err := tarWriter.WriteHeader(originalHeader); err != nil {
pipeWriter.CloseWithError(err) _ = pipeWriter.CloseWithError(err)
return return
} }
if err := copyWithBuffer(tarWriter, tarReader); err != nil { if err := copyWithBuffer(tarWriter, tarReader); err != nil {
pipeWriter.CloseWithError(err) _ = pipeWriter.CloseWithError(err)
return return
} }
continue continue
@@ -184,7 +199,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
delete(mods, originalHeader.Name) delete(mods, originalHeader.Name)
if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil {
pipeWriter.CloseWithError(err) _ = pipeWriter.CloseWithError(err)
return return
} }
} }
@@ -192,12 +207,12 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
// Apply the modifiers that haven't matched any files in the archive // Apply the modifiers that haven't matched any files in the archive
for name, modifier := range mods { for name, modifier := range mods {
if err := modify(name, nil, modifier, nil); err != nil { if err := modify(name, nil, modifier, nil); err != nil {
pipeWriter.CloseWithError(err) _ = pipeWriter.CloseWithError(err)
return return
} }
} }
pipeWriter.Close() _ = pipeWriter.Close()
}() }()
return pipeReader return pipeReader
} }
@@ -218,7 +233,7 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro
hdr.ModTime = hdr.ModTime.Truncate(time.Second) hdr.ModTime = hdr.ModTime.Truncate(time.Second)
hdr.AccessTime = time.Time{} hdr.AccessTime = time.Time{}
hdr.ChangeTime = time.Time{} hdr.ChangeTime = time.Time{}
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) hdr.Mode = chmodTarEntry(hdr.Mode)
hdr.Name = canonicalTarName(name, fi.IsDir()) hdr.Name = canonicalTarName(name, fi.IsDir())
return hdr, nil return hdr, nil
} }
@@ -227,7 +242,7 @@ const paxSchilyXattr = "SCHILY.xattr."
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
// to a tar header // to a tar header
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error {
const ( const (
// Values based on linux/include/uapi/linux/capability.h // Values based on linux/include/uapi/linux/capability.h
xattrCapsSz2 = 20 xattrCapsSz2 = 20
@@ -235,7 +250,7 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
vfsCapRevision2 = 2 vfsCapRevision2 = 2
vfsCapRevision3 = 3 vfsCapRevision3 = 3
) )
capability, _ := lgetxattr(path, "security.capability") capability, _ := lgetxattr(filePath, "security.capability")
if capability != nil { if capability != nil {
if capability[versionOffset] == vfsCapRevision3 { if capability[versionOffset] == vfsCapRevision3 {
// Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no
@@ -292,9 +307,10 @@ func canonicalTarName(name string, isDir bool) string {
return name return name
} }
// addTarFile adds to the tar archive a file from `path` as `name` // addTarFile adds to the tar archive a file from `srcPath` as `name`
func (ta *tarAppender) addTarFile(path, name string) error { func (ta *tarAppender) addTarFile(srcPath, archivePath string) error {
fi, err := os.Lstat(path) archivePath = filepath.ToSlash(archivePath)
fi, err := os.Lstat(srcPath)
if err != nil { if err != nil {
return err return err
} }
@@ -302,17 +318,17 @@ func (ta *tarAppender) addTarFile(path, name string) error {
var link string var link string
if fi.Mode()&os.ModeSymlink != 0 { if fi.Mode()&os.ModeSymlink != 0 {
var err error var err error
link, err = os.Readlink(path) link, err = os.Readlink(srcPath)
if err != nil { if err != nil {
return err return err
} }
} }
hdr, err := FileInfoHeader(name, fi, link) hdr, err := FileInfoHeader(archivePath, fi, link)
if err != nil { if err != nil {
return err return err
} }
if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil {
return err return err
} }
@@ -321,7 +337,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if !fi.IsDir() && hasHardlinks(fi) { if !fi.IsDir() && hasHardlinks(fi) {
inode, err := getInodeFromStat(fi.Sys()) inode, err := getInodeFromStat(fi.Sys())
if err != nil { 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 // a link should have a name that it links too
// and that linked name should be first in the tar archive // and that linked name should be first in the tar archive
@@ -330,7 +346,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
hdr.Linkname = oldpath hdr.Linkname = oldpath
hdr.Size = 0 // This Must be here for the writer math to add up! hdr.Size = 0 // This Must be here for the writer math to add up!
} else { } else {
ta.SeenFiles[inode] = name ta.SeenFiles[inode] = hdr.Name
} }
} }
@@ -359,7 +375,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
} }
if ta.WhiteoutConverter != nil { if ta.WhiteoutConverter != nil {
wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi)
if err != nil { if err != nil {
return err return err
} }
@@ -370,12 +386,12 @@ func (ta *tarAppender) addTarFile(path, name string) error {
// hdr may have been updated to be a whiteout with returning // hdr may have been updated to be a whiteout with returning
// a whiteout header // a whiteout header
if wo != nil { 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 { if err := ta.TarWriter.WriteHeader(hdr); err != nil {
return err return err
} }
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
return fmt.Errorf("tar: cannot use whiteout for non-empty file")
}
hdr = wo hdr = wo
} }
} }
@@ -387,13 +403,13 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
// We use sequential file access to avoid depleting the standby list on // We use sequential file access to avoid depleting the standby list on
// Windows. On Linux, this equates to a regular os.Open. // Windows. On Linux, this equates to a regular os.Open.
file, err := sequential.Open(path) file, err := sequential.Open(srcPath)
if err != nil { if err != nil {
return err return err
} }
err = copyWithBuffer(ta.TarWriter, file) err = copyWithBuffer(ta.TarWriter, file)
file.Close() _ = file.Close()
if err != nil { if err != nil {
return err return err
} }
@@ -402,7 +418,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
return nil 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 ( var (
Lchown = true Lchown = true
inUserns, bestEffortXattrs bool inUserns, bestEffortXattrs bool
@@ -426,8 +442,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeDir: case tar.TypeDir:
// Create directory unless it exists as a directory already. // Create directory unless it exists as a directory already.
// In that case we just want to merge the two // In that case we just want to merge the two
if fi, err := os.Lstat(path); err != nil || !fi.IsDir() { if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() {
if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil {
return err return err
} }
} }
@@ -435,7 +451,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeReg: case tar.TypeReg:
// Source is regular file. We use sequential file access to avoid depleting // 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. // 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 { if err != nil {
return err return err
} }
@@ -447,20 +463,20 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeBlock, tar.TypeChar: case tar.TypeBlock, tar.TypeChar:
if inUserns { // cannot create devices in a userns 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 return nil
} }
// Handle this is an OS-specific way // Handle this is an OS-specific way
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
return err return err
} }
case tar.TypeFifo: case tar.TypeFifo:
// Handle this is an OS-specific way // 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) { if inUserns && errors.Is(err, syscall.EPERM) {
// In most cases, cannot create a fifo if running in user namespace // 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 nil
} }
return err return err
@@ -468,26 +484,26 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeLink: case tar.TypeLink:
// #nosec G305 -- The target path is checked for path traversal. // #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 // check for hardlink breakout
if !strings.HasPrefix(targetPath, extractDir) { if !strings.HasPrefix(linkTarget, extractDir) {
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) 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 return err
} }
case tar.TypeSymlink: case tar.TypeSymlink:
// path -> hdr.Linkname = targetPath // path -> hdr.Linkname = targetPath
// e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file // 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 // 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: // that symlink would first have to be created, which would be caught earlier, at this very check:
if !strings.HasPrefix(targetPath, extractDir) { 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 return err
} }
@@ -504,12 +520,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
if chownOpts == nil { if chownOpts == nil {
chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid} 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 var msg string
if inUserns && errors.Is(err, syscall.EINVAL) { if inUserns && errors.Is(err, syscall.EINVAL) {
msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" 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 +535,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
if !ok { if !ok {
continue 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) { if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) {
// EPERM occurs if modifying xattrs is not allowed. This can // EPERM occurs if modifying xattrs is not allowed. This can
// happen when running in userns with restrictions (ChromeOS). // happen when running in userns with restrictions (ChromeOS).
@@ -538,39 +554,43 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
// There is no LChmod, so ignore mode for symlink. Also, this // There is no LChmod, so ignore mode for symlink. Also, this
// must happen after chown, as that can modify the file mode // 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 return err
} }
aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime))
mTime := boundTime(hdr.ModTime) mTime := boundTime(hdr.ModTime)
// chtimes doesn't support a NOFOLLOW flag atm switch hdr.Typeflag {
if hdr.Typeflag == tar.TypeLink { case tar.TypeSymlink:
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { // Apply timestamps to the symlink itself (AT_SYMLINK_NOFOLLOW).
if err := chtimes(path, aTime, mTime); err != nil { 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 {
return err return err
} }
} }
} else if hdr.Typeflag != tar.TypeSymlink { default:
if err := chtimes(path, aTime, mTime); err != nil { // All other file types follow symlinks.
return err if err := chtimes(dstPath, aTime, mTime); err != nil {
}
} else {
if err := lchtimes(path, aTime, mTime); err != nil {
return err return err
} }
} }
return nil 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. // stream of bytes.
func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) {
return TarWithOptions(path, &TarOptions{Compression: comp}) 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`. // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
tb, err := NewTarballer(srcPath, options) tb, err := NewTarballer(srcPath, options)
@@ -805,6 +825,9 @@ func (t *Tarballer) Do() {
// Unpack unpacks the decompressedArchive to dest with options. // Unpack unpacks the decompressedArchive to dest with options.
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error {
if options == nil {
options = &TarOptions{}
}
tr := tar.NewReader(decompressedArchive) tr := tar.NewReader(decompressedArchive)
var dirs []*tar.Header var dirs []*tar.Header
@@ -846,8 +869,8 @@ loop:
} }
// #nosec G305 -- The joined path is checked for path traversal. // #nosec G305 -- The joined path is checked for path traversal.
path := filepath.Join(dest, hdr.Name) dstPath := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path) rel, err := filepath.Rel(dest, dstPath)
if err != nil { if err != nil {
return err return err
} }
@@ -855,21 +878,21 @@ loop:
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) 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 exists we almost always just want to remove and replace it.
// The only exception is when it is a directory *and* the file from // The 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. // the layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer). // 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 options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir {
// If NoOverwriteDirNonDir is true then we cannot replace // If NoOverwriteDirNonDir is true then we cannot replace
// an existing directory with a non-directory from the archive. // 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 options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir {
// If NoOverwriteDirNonDir is true then we cannot replace // If NoOverwriteDirNonDir is true then we cannot replace
// an existing non-directory with a directory from the archive. // 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 == "." { if fi.IsDir() && hdr.Name == "." {
@@ -877,7 +900,7 @@ loop:
} }
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(dstPath); err != nil {
return err return err
} }
} }
@@ -888,7 +911,7 @@ loop:
} }
if whiteoutConverter != nil { if whiteoutConverter != nil {
writeFile, err := whiteoutConverter.ConvertRead(hdr, path) writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath)
if err != nil { if err != nil {
return err return err
} }
@@ -897,7 +920,7 @@ loop:
} }
} }
if err := createTarFile(path, dest, hdr, tr, options); err != nil { if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil {
return err return err
} }
@@ -910,9 +933,8 @@ loop:
for _, hdr := range dirs { for _, hdr := range dirs {
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name) dstPath := filepath.Join(dest, hdr.Name)
if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
return err return err
} }
} }
@@ -923,16 +945,15 @@ loop:
// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is // 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 // 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. // 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 { func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error {
// Not the root directory, ensure that the parent directory exists // For non-directory entries, ensure that the parent directory exists.
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { if hdr.Typeflag != tar.TypeDir {
parent := filepath.Dir(hdr.Name) parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent) parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { 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 // 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 // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche
// usage that reduces the portability of an image. // usage that reduces the portability of an image.
@@ -974,9 +995,6 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
if options == nil { if options == nil {
options = &TarOptions{} options = &TarOptions{}
} }
if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{}
}
r := tarArchive r := tarArchive
if decompress { if decompress {
@@ -984,7 +1002,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
if err != nil { if err != nil {
return err return err
} }
defer decompressedArchive.Close() defer func() { _ = decompressedArchive.Close() }()
r = decompressedArchive r = decompressedArchive
} }
@@ -998,7 +1016,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer archive.Close() defer func() { _ = archive.Close() }()
return archiver.Untar(archive, dst, &TarOptions{ return archiver.Untar(archive, dst, &TarOptions{
IDMap: archiver.IDMapping, IDMap: archiver.IDMapping,
}) })
@@ -1010,7 +1028,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer archive.Close() defer func() { _ = archive.Close() }()
return archiver.Untar(archive, dst, &TarOptions{ return archiver.Untar(archive, dst, &TarOptions{
IDMap: archiver.IDMapping, IDMap: archiver.IDMapping,
}) })
@@ -1070,13 +1088,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
defer close(errC) defer close(errC)
errC <- func() error { errC <- func() error {
defer w.Close() defer func() { _ = w.Close() }()
srcF, err := os.Open(src) srcF, err := os.Open(src)
if err != nil { if err != nil {
return err return err
} }
defer srcF.Close() defer func() { _ = srcF.Close() }()
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
if err != nil { if err != nil {
@@ -1087,14 +1105,14 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
hdr.AccessTime = time.Time{} hdr.AccessTime = time.Time{}
hdr.ChangeTime = time.Time{} hdr.ChangeTime = time.Time{}
hdr.Name = filepath.Base(dst) hdr.Name = filepath.Base(dst)
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) hdr.Mode = chmodTarEntry(hdr.Mode)
if err := remapIDs(archiver.IDMapping, hdr); err != nil { if err := remapIDs(archiver.IDMapping, hdr); err != nil {
return err return err
} }
tw := tar.NewWriter(w) tw := tar.NewWriter(w)
defer tw.Close() defer func() { _ = tw.Close() }()
if err := tw.WriteHeader(hdr); err != nil { if err := tw.WriteHeader(hdr); err != nil {
return err return err
} }
@@ -1112,7 +1130,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
err = archiver.Untar(r, filepath.Dir(dst), nil) err = archiver.Untar(r, filepath.Dir(dst), nil)
if err != nil { if err != nil {
r.CloseWithError(err) _ = r.CloseWithError(err)
} }
return err return err
} }
+59 -37
View File
@@ -4,6 +4,7 @@ import (
"archive/tar" "archive/tar"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -13,36 +14,43 @@ import (
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
if format == OverlayWhiteoutFormat { if format == OverlayWhiteoutFormat {
return overlayWhiteoutConverter{} return newOverlayWhiteoutConverter()
} }
return nil return nil
} }
type overlayWhiteoutConverter struct{} type overlayWhiteoutConverter struct {
opaqueXattr string
}
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) { func newOverlayWhiteoutConverter() overlayWhiteoutConverter {
opaqueXattr := "trusted.overlay.opaque"
if userns.RunningInUserNS() {
opaqueXattr = "user.overlay.opaque"
}
return overlayWhiteoutConverter{
opaqueXattr: opaqueXattr,
}
}
func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ error) {
// convert whiteouts to AUFS format // convert whiteouts to AUFS format
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
// we just rename the file and make it normal // we just rename the file and make it normal
dir, filename := filepath.Split(hdr.Name) dir, filename := path.Split(hdr.Name)
hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) hdr.Name = path.Join(dir, WhiteoutPrefix+filename)
hdr.Mode = 0o600 hdr.Mode = 0o600
hdr.Typeflag = tar.TypeReg hdr.Typeflag = tar.TypeReg
hdr.Size = 0 hdr.Size = 0
} }
if fi.Mode()&os.ModeDir == 0 { if !fi.IsDir() {
// FIXME(thaJeztah): return a sentinel error instead of nil, nil // FIXME(thaJeztah): return a sentinel error instead of nil, nil
return 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 // convert opaque dirs to AUFS format by writing an empty file with the prefix
opaque, err := lgetxattr(path, opaqueXattrName) opaque, err := lgetxattr(filePath, c.opaqueXattr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -50,14 +58,14 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
// FIXME(thaJeztah): return a sentinel error instead of nil, nil // FIXME(thaJeztah): return a sentinel error instead of nil, nil
return nil, nil return nil, nil
} }
delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr)
// create a header for the whiteout file // create a header for the whiteout file
// it should inherit some properties from the parent, but be a regular file // it should inherit some properties from the parent, but be a regular file
return &tar.Header{ return &tar.Header{
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
Mode: hdr.Mode & int64(os.ModePerm), 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, Size: 0,
Uid: hdr.Uid, Uid: hdr.Uid,
Uname: hdr.Uname, Uname: hdr.Uname,
@@ -68,40 +76,54 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
}, nil }, nil
} }
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) {
base := filepath.Base(path) base := filepath.Base(filePath)
dir := filepath.Dir(path) dir := filepath.Dir(filePath)
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay switch base {
if base == WhiteoutOpaqueDir { case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..":
opaqueXattrName := "trusted.overlay.opaque" return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name)
if userns.RunningInUserNS() {
opaqueXattrName = "user.overlay.opaque"
}
err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0) case WhiteoutOpaqueDir:
if err != nil { // If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay.
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err) 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)
// don't write the file itself
return false, err
} }
// Don't write the whiteout file itself.
return false, nil
// if a file was deleted and we are using overlay, we need to create a character device default:
if strings.HasPrefix(base, WhiteoutPrefix) { originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix)
originalBase := base[len(WhiteoutPrefix):] if !ok {
// Regular file.
return true, nil
}
// If a file was deleted, and we are using overlay, we need to create a character device.
originalPath := filepath.Join(dir, originalBase) originalPath := filepath.Join(dir, originalBase)
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { 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) return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err)
} }
if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
// 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 return false, err
} }
}
// don't write the file itself // Don't write the whiteout file itself.
return false, nil return false, nil
} }
return true, nil
} }
+24 -11
View File
@@ -5,6 +5,8 @@ package archive
import ( import (
"archive/tar" "archive/tar"
"errors" "errors"
"fmt"
"math"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -13,6 +15,8 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
var errInvalidArchive = errors.New("invalid archive")
// addLongPathPrefix adds the Windows long path prefix to the path provided if // 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. // it does not already have it. It is a no-op on platforms other than Windows.
func addLongPathPrefix(srcPath string) string { func addLongPathPrefix(srcPath string) string {
@@ -29,20 +33,19 @@ func getWalkRoot(srcPath string, include string) string {
// chmodTarEntry is used to adjust the file permissions used in tar header based // chmodTarEntry is used to adjust the file permissions used in tar header based
// on the platform the archival is done. // on the platform the archival is done.
func chmodTarEntry(perm os.FileMode) os.FileMode { func chmodTarEntry(mode int64) int64 {
return perm // noop for unix as golang APIs provide perm bits correctly return mode // 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) s, ok := stat.(*syscall.Stat_t)
if !ok { if !ok {
// FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897 return 0, fmt.Errorf("unexpected stat type %T", stat)
return 0, nil
} }
return s.Ino, nil return s.Ino, nil
} }
func getFileUIDGID(stat interface{}) (int, int, error) { func getFileUIDGID(stat any) (int, int, error) {
s, ok := stat.(*syscall.Stat_t) s, ok := stat.(*syscall.Stat_t)
if !ok { 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, // Creating device nodes is not supported when running in a user namespace,
// produces a [syscall.EPERM] in most cases. // 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) mode := uint32(hdr.Mode & 0o7777)
switch hdr.Typeflag { switch hdr.Typeflag {
case tar.TypeBlock: case tar.TypeBlock:
@@ -67,18 +70,28 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
mode |= unix.S_IFIFO 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)
} }
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
}
func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error {
if hdr.Typeflag == tar.TypeLink { if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { 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 return err
} }
} }
} else if hdr.Typeflag != tar.TypeSymlink { } 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 return err
} }
} }
+5 -5
View File
@@ -33,15 +33,15 @@ func getWalkRoot(srcPath string, include string) string {
// chmodTarEntry is used to adjust the file permissions used in tar header based // chmodTarEntry is used to adjust the file permissions used in tar header based
// on the platform the archival is done. // on the platform the archival is done.
func chmodTarEntry(perm os.FileMode) os.FileMode { func chmodTarEntry(mode int64) int64 {
// Remove group- and world-writable bits. // Remove group- and world-writable bits.
perm &= 0o755 mode &= 0o755
// Add the x bit: make everything +x on Windows // Add the x bit: make everything +x on Windows
return perm | 0o111 return mode | 0o111
} }
func getInodeFromStat(stat interface{}) (uint64, error) { func getInodeFromStat(stat any) (uint64, error) {
// do nothing. no notion of Inode in stat on Windows // do nothing. no notion of Inode in stat on Windows
return 0, nil return 0, nil
} }
@@ -56,7 +56,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
return nil 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 // no notion of file ownership mapping yet on Windows
return 0, 0, nil return 0, 0, nil
} }
+9 -9
View File
@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@@ -217,8 +218,8 @@ func (info *FileInfo) LookUp(path string) *FileInfo {
return info return info
} }
pathElements := strings.Split(path, string(os.PathSeparator)) pathElements := strings.SplitSeq(path, string(os.PathSeparator))
for _, elem := range pathElements { for elem := range pathElements {
if elem != "" { if elem != "" {
child := parent.children[elem] child := parent.children[elem]
if child == nil { if child == nil {
@@ -256,9 +257,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
// otherwise any previous delete/change is considered recursive // otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo) oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() { if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children { maps.Copy(oldChildren, oldInfo.children)
oldChildren[k] = v
}
} }
for name, newChild := range info.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) whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
timestamp := time.Now() timestamp := time.Now()
hdr := &tar.Header{ hdr := &tar.Header{
Name: whiteOut[1:], Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"),
Size: 0, Size: 0,
ModTime: timestamp, ModTime: timestamp,
AccessTime: 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) log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err)
} }
} else { } else {
path := filepath.Join(dir, change.Path) srcPath := filepath.Join(dir, change.Path)
if err := ta.addTarFile(path, change.Path[1:]); err != nil { archivePath := strings.TrimPrefix(filepath.ToSlash(change.Path), "/")
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err) if err := ta.addTarFile(srcPath, archivePath); err != nil {
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", srcPath, err)
} }
} }
} }
+1 -1
View File
@@ -265,7 +265,7 @@ func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno)
} }
func clen(n []byte) int { func clen(n []byte) int {
for i := 0; i < len(n); i++ { for i := range n {
if n[i] == 0 { if n[i] == 0 {
return i return i
} }
+1 -1
View File
@@ -26,7 +26,7 @@ func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, err
}() }()
// block until both routines have returned // block until both routines have returned
for i := 0; i < 2; i++ { for range 2 {
if err := <-errs; err != nil { if err := <-errs; err != nil {
return nil, nil, err return nil, nil, err
} }
+2 -2
View File
@@ -66,7 +66,7 @@ type nopWriteCloser struct {
func (nopWriteCloser) Close() error { return nil } func (nopWriteCloser) Close() error { return nil }
var bufioReader32KPool = &sync.Pool{ 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 { 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) log.G(ctx).Debugf("Using %s to decompress", unpigzPath)
return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) // #nosec G204 -- Subprocess launched with variable
} }
// cmdStream executes a command, and returns its stdout as a stream. // cmdStream executes a command, and returns its stdout as a stream.
+14 -13
View File
@@ -22,7 +22,7 @@ var (
) )
var copyPool = sync.Pool{ 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 { 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 // RebaseArchiveEntries rewrites the given srcContent archive replacing
// an occurrence of oldBase with newBase at the beginning of entry names. // an occurrence of oldBase with newBase at the beginning of entry names.
func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser { 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 // If oldBase specifies the root directory, use an empty string as
// oldBase instead so that newBase doesn't replace the path separator // oldBase instead so that newBase doesn't replace the path separator
// that all paths will start with. // that all paths will start with.
@@ -336,12 +339,12 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
hdr, err := srcTar.Next() hdr, err := srcTar.Next()
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
// Signals end of archive. // Signals end of archive.
rebasedTar.Close() _ = rebasedTar.Close()
w.Close() _ = w.Close()
return return
} }
if err != nil { if err != nil {
w.CloseWithError(err) _ = w.CloseWithError(err)
return return
} }
@@ -359,7 +362,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
} }
if err = rebasedTar.WriteHeader(hdr); err != nil { if err = rebasedTar.WriteHeader(hdr); err != nil {
w.CloseWithError(err) _ = w.CloseWithError(err)
return return
} }
@@ -374,7 +377,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
// not be vulnerable to this code consuming memory. // not be vulnerable to this code consuming memory.
//nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec) //nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec)
if _, err = io.Copy(rebasedTar, srcTar); err != nil { if _, err = io.Copy(rebasedTar, srcTar); err != nil {
w.CloseWithError(err) _ = w.CloseWithError(err)
return return
} }
} }
@@ -408,7 +411,7 @@ func CopyResource(srcPath, dstPath string, followLink bool) error {
if err != nil { if err != nil {
return err return err
} }
defer content.Close() defer func() { _ = content.Close() }()
return CopyTo(content, srcInfo, dstPath) return CopyTo(content, srcInfo, dstPath)
} }
@@ -427,14 +430,12 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error {
if err != nil { if err != nil {
return err return err
} }
defer copyArchive.Close() defer func() { _ = copyArchive.Close() }()
options := &TarOptions{ return Untar(copyArchive, dstDir, &TarOptions{
NoLchown: true, NoLchown: true,
NoOverwriteDirNonDir: true, NoOverwriteDirNonDir: true,
} })
return Untar(copyArchive, dstDir, options)
} }
// ResolveHostSourcePath decides real path need to be copied with parameters such as // ResolveHostSourcePath decides real path need to be copied with parameters such as
+1 -1
View File
@@ -5,5 +5,5 @@ package archive
import "golang.org/x/sys/unix" import "golang.org/x/sys/unix"
func mknod(path string, mode uint32, dev uint64) error { func mknod(path string, mode uint32, dev uint64) error {
return unix.Mknod(path, mode, int(dev)) return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
} }
+12 -15
View File
@@ -28,9 +28,6 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if options == nil { if options == nil {
options = &TarOptions{} options = &TarOptions{}
} }
if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{}
}
aufsTempdir := "" aufsTempdir := ""
aufsHardlinks := make(map[string]*tar.Header) aufsHardlinks := make(map[string]*tar.Header)
@@ -102,8 +99,8 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
} }
} }
// #nosec G305 -- The joined path is guarded against path traversal. // #nosec G305 -- The joined path is guarded against path traversal.
path := filepath.Join(dest, hdr.Name) dstPath := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path) rel, err := filepath.Rel(dest, dstPath)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -112,10 +109,10 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) 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) { if strings.HasPrefix(base, WhiteoutPrefix) {
dir := filepath.Dir(path) dir := filepath.Dir(dstPath)
if base == WhiteoutOpaqueDir { if base == WhiteoutOpaqueDir {
_, err := os.Lstat(dir) _, err := os.Lstat(dir)
if err != nil { if err != nil {
@@ -132,7 +129,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return nil return nil
} }
if _, exists := unpackedPaths[path]; !exists { if _, exists := unpackedPaths[path]; !exists {
return os.RemoveAll(path) return os.RemoveAll(path) // #nosec G122 -- FIXME: consider root-scoped APIs (e.g. os.Root) to prevent symlink TOCTOU traversal
} }
return nil return nil
}) })
@@ -147,13 +144,13 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
} }
} }
} else { } else {
// If path exits we almost always just want to remove and replace it. // If dstPath exists we almost always just want to remove and replace it.
// The only exception is when it is a directory *and* the file from // The 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. // the layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer). // 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 !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(dstPath); err != nil {
return 0, err return 0, err
} }
} }
@@ -182,7 +179,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return 0, err 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 return 0, err
} }
@@ -191,14 +188,14 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if hdr.Typeflag == tar.TypeDir { if hdr.Typeflag == tar.TypeDir {
dirs = append(dirs, hdr) dirs = append(dirs, hdr)
} }
unpackedPaths[path] = struct{}{} unpackedPaths[dstPath] = struct{}{}
} }
} }
for _, hdr := range dirs { for _, hdr := range dirs {
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name) dstPath := filepath.Join(dest, hdr.Name)
if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil {
return 0, err return 0, err
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@ func (fi nosysFileInfo) Gname() (string, error) {
return "", nil 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. // A Sys value of type *tar.Header is safe as it is system-independent.
// The tar.FileInfoHeader function copies the fields into the returned // The tar.FileInfoHeader function copies the fields into the returned
// header without performing any OS lookups. // header without performing any OS lookups.
+5 -4
View File
@@ -36,10 +36,11 @@ func sysStat(fi os.FileInfo, hdr *tar.Header) error {
hdr.Uid = int(s.Uid) hdr.Uid = int(s.Uid)
hdr.Gid = int(s.Gid) hdr.Gid = int(s.Gid)
if s.Mode&unix.S_IFBLK != 0 || if s.Mode&unix.S_IFBLK != 0 || s.Mode&unix.S_IFCHR != 0 {
s.Mode&unix.S_IFCHR != 0 { // #nosec G115 -- Rdev type varies by platform.
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert rdev := uint64(s.Rdev) //nolint:unconvert // Rdev type varies by platform.
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert hdr.Devmajor = int64(unix.Major(rdev))
hdr.Devminor = int64(unix.Minor(rdev))
} }
return nil return nil
+10 -10
View File
@@ -13,26 +13,26 @@ import (
// lgetxattr retrieves the value of the extended attribute identified by attr // lgetxattr retrieves the value of the extended attribute identified by attr
// and associated with the given path in the file system. // and associated with the given path in the file system.
// It returns a nil slice and nil error if the xattr is not set. // 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 // Start with a 128 length byte array
dest := make([]byte, 128) dest := make([]byte, 128)
sz, err := unix.Lgetxattr(path, attr, dest) sz, err := unix.Lgetxattr(filePath, attr, dest)
for errors.Is(err, unix.ERANGE) { for errors.Is(err, unix.ERANGE) {
// Buffer too small, use zero-sized buffer to get the actual size // 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 { if err != nil {
return nil, wrapPathError("lgetxattr", path, attr, err) return nil, wrapPathError("lgetxattr", filePath, attr, err)
} }
dest = make([]byte, sz) dest = make([]byte, sz)
sz, err = unix.Lgetxattr(path, attr, dest) sz, err = unix.Lgetxattr(filePath, attr, dest)
} }
if err != nil { if err != nil {
if errors.Is(err, noattr) { if errors.Is(err, noattr) {
return nil, nil return nil, nil
} }
return nil, wrapPathError("lgetxattr", path, attr, err) return nil, wrapPathError("lgetxattr", filePath, attr, err)
} }
return dest[:sz], nil 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 // lsetxattr sets the value of the extended attribute identified by attr
// and associated with the given path in the file system. // and associated with the given path in the file system.
func lsetxattr(path string, attr string, data []byte, flags int) error { func lsetxattr(filePath string, attr string, data []byte, flags int) error {
return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags)) 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 { if err == nil {
return 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)}
} }
+3 -3
View File
@@ -644,7 +644,7 @@ github.com/mitchellh/go-wordwrap
# github.com/mitchellh/hashstructure/v2 v2.0.2 # github.com/mitchellh/hashstructure/v2 v2.0.2
## explicit; go 1.14 ## explicit; go 1.14
github.com/mitchellh/hashstructure/v2 github.com/mitchellh/hashstructure/v2
# github.com/moby/buildkit v0.32.0-rc1 # github.com/moby/buildkit v0.32.0-rc2
## explicit; go 1.26.3 ## explicit; go 1.26.3
github.com/moby/buildkit/api/services/control github.com/moby/buildkit/api/services/control
github.com/moby/buildkit/api/types github.com/moby/buildkit/api/types
@@ -747,8 +747,8 @@ github.com/moby/buildkit/version
# github.com/moby/docker-image-spec v1.3.1 # github.com/moby/docker-image-spec v1.3.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/moby/docker-image-spec/specs-go/v1 github.com/moby/docker-image-spec/specs-go/v1
# github.com/moby/go-archive v0.2.0 # github.com/moby/go-archive v0.2.1
## explicit; go 1.23.0 ## explicit; go 1.25
github.com/moby/go-archive github.com/moby/go-archive
github.com/moby/go-archive/compression github.com/moby/go-archive/compression
github.com/moby/go-archive/tarheader github.com/moby/go-archive/tarheader