vendor: github.com/moby/go-archive v0.2.1

full diff: https://github.com/moby/go-archive/compare/v0.2.0...v0.2.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-07-17 12:02:08 +02:00
parent 720ec135a4
commit 53b2d03bb3
21 changed files with 280 additions and 222 deletions

2
go.mod
View File

@@ -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.0
github.com/moby/go-archive v0.2.1
github.com/moby/locker v1.0.1
github.com/moby/patternmatcher v0.6.1
github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4

4
go.sum
View File

@@ -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.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/go-archive v0.2.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc=
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/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=

View File

@@ -9,6 +9,7 @@ issues:
linters:
enable:
- errorlint
- gosec
- unconvert
- unparam
exclusions:
@@ -16,7 +17,18 @@ 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

View File

@@ -46,9 +46,18 @@ type (
// TarOptions wraps the tar options.
TarOptions struct {
IncludeFiles []string
ExcludePatterns []string
Compression compression.Compression
// 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.
NoLchown bool
IDMap user.IdentityMapping
ChownOpts *ChownOpts
@@ -86,10 +95,14 @@ func NewDefaultArchiver() *Archiver {
return &Archiver{Untar: Untar}
}
// 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
// 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}
}
const (
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
// starts with a tar file header.
func IsArchivePath(path string) bool {
file, err := os.Open(path)
func IsArchivePath(filePath string) bool {
file, err := os.Open(filePath)
if err != nil {
return false
}
defer file.Close()
defer func() { _ = file.Close() }()
rdr, err := compression.DecompressStream(file)
if err != nil {
return false
}
defer rdr.Close()
defer func() { _ = rdr.Close() }()
r := tar.NewReader(rdr)
_, err = r.Next()
return err == nil
@@ -129,8 +142,10 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
go func() {
tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter)
defer inputTarStream.Close()
defer tarWriter.Close()
defer func() {
_ = tarWriter.Close()
_ = inputTarStream.Close()
}()
modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error {
header, data, err := modifier(name, original, tarReader)
@@ -164,7 +179,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
break
}
if err != nil {
pipeWriter.CloseWithError(err)
_ = pipeWriter.CloseWithError(err)
return
}
@@ -172,11 +187,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
if !ok {
// No modifiers for this file, copy the header and data
if err := tarWriter.WriteHeader(originalHeader); err != nil {
pipeWriter.CloseWithError(err)
_ = pipeWriter.CloseWithError(err)
return
}
if err := copyWithBuffer(tarWriter, tarReader); err != nil {
pipeWriter.CloseWithError(err)
_ = pipeWriter.CloseWithError(err)
return
}
continue
@@ -184,7 +199,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
delete(mods, originalHeader.Name)
if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil {
pipeWriter.CloseWithError(err)
_ = pipeWriter.CloseWithError(err)
return
}
}
@@ -192,12 +207,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
}
@@ -218,7 +233,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 = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
hdr.Mode = chmodTarEntry(hdr.Mode)
hdr.Name = canonicalTarName(name, fi.IsDir())
return hdr, nil
}
@@ -227,7 +242,7 @@ const paxSchilyXattr = "SCHILY.xattr."
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
// to a tar header
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error {
const (
// Values based on linux/include/uapi/linux/capability.h
xattrCapsSz2 = 20
@@ -235,7 +250,7 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
vfsCapRevision2 = 2
vfsCapRevision3 = 3
)
capability, _ := lgetxattr(path, "security.capability")
capability, _ := lgetxattr(filePath, "security.capability")
if capability != nil {
if capability[versionOffset] == vfsCapRevision3 {
// Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no
@@ -292,9 +307,10 @@ func canonicalTarName(name string, isDir bool) string {
return name
}
// addTarFile adds to the tar archive a file from `path` as `name`
func (ta *tarAppender) addTarFile(path, name string) error {
fi, err := os.Lstat(path)
// addTarFile adds to the tar archive a file from `srcPath` as `name`
func (ta *tarAppender) addTarFile(srcPath, archivePath string) error {
archivePath = filepath.ToSlash(archivePath)
fi, err := os.Lstat(srcPath)
if err != nil {
return err
}
@@ -302,17 +318,17 @@ func (ta *tarAppender) addTarFile(path, name string) error {
var link string
if fi.Mode()&os.ModeSymlink != 0 {
var err error
link, err = os.Readlink(path)
link, err = os.Readlink(srcPath)
if err != nil {
return err
}
}
hdr, err := FileInfoHeader(name, fi, link)
hdr, err := FileInfoHeader(archivePath, fi, link)
if err != nil {
return err
}
if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil {
if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil {
return err
}
@@ -321,7 +337,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if !fi.IsDir() && hasHardlinks(fi) {
inode, err := getInodeFromStat(fi.Sys())
if err != nil {
return err
return fmt.Errorf("unexpected file info for %q: %w", srcPath, err)
}
// a link should have a name that it links too
// and that linked name should be first in the tar archive
@@ -330,7 +346,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
hdr.Linkname = oldpath
hdr.Size = 0 // This Must be here for the writer math to add up!
} else {
ta.SeenFiles[inode] = name
ta.SeenFiles[inode] = hdr.Name
}
}
@@ -359,7 +375,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
}
if ta.WhiteoutConverter != nil {
wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi)
wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi)
if err != nil {
return err
}
@@ -370,12 +386,12 @@ func (ta *tarAppender) addTarFile(path, name 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
}
}
@@ -387,13 +403,13 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
// We use sequential file access to avoid depleting the standby list on
// Windows. On Linux, this equates to a regular os.Open.
file, err := sequential.Open(path)
file, err := sequential.Open(srcPath)
if err != nil {
return err
}
err = copyWithBuffer(ta.TarWriter, file)
file.Close()
_ = file.Close()
if err != nil {
return err
}
@@ -402,7 +418,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
return nil
}
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
var (
Lchown = true
inUserns, bestEffortXattrs bool
@@ -426,8 +442,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeDir:
// Create directory unless it exists as a directory already.
// In that case we just want to merge the two
if fi, err := os.Lstat(path); err != nil || !fi.IsDir() {
if err := os.Mkdir(path, hdrInfo.Mode()); err != nil {
if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() {
if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil {
return err
}
}
@@ -435,7 +451,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeReg:
// Source is regular file. We use sequential file access to avoid depleting
// the standby list on Windows. On Linux, this equates to a regular os.OpenFile.
file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
file, err := sequential.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
if err != nil {
return err
}
@@ -447,20 +463,20 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeBlock, tar.TypeChar:
if inUserns { // cannot create devices in a userns
log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns")
log.G(context.TODO()).WithFields(log.Fields{"path": dstPath, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns")
return nil
}
// Handle this is an OS-specific way
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
return err
}
case tar.TypeFifo:
// Handle this is an OS-specific way
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
if inUserns && errors.Is(err, syscall.EPERM) {
// In most cases, cannot create a fifo if running in user namespace
log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns")
log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": dstPath, "type": hdr.Typeflag}).Debug("creating fifo node in a userns")
return nil
}
return err
@@ -468,26 +484,26 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
case tar.TypeLink:
// #nosec G305 -- The target path is checked for path traversal.
targetPath := filepath.Join(extractDir, hdr.Linkname)
linkTarget := filepath.Join(extractDir, hdr.Linkname)
// check for hardlink breakout
if !strings.HasPrefix(targetPath, extractDir) {
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
if !strings.HasPrefix(linkTarget, extractDir) {
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", linkTarget, hdr.Linkname))
}
if err := os.Link(targetPath, path); err != nil {
if err := os.Link(linkTarget, dstPath); err != nil {
return err
}
case tar.TypeSymlink:
// path -> hdr.Linkname = targetPath
// e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file
targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal.
targetPath := filepath.Join(filepath.Dir(dstPath), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal.
// the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because
// that symlink would first have to be created, which would be caught earlier, at this very check:
if !strings.HasPrefix(targetPath, extractDir) {
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname))
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", dstPath, hdr.Linkname))
}
if err := os.Symlink(hdr.Linkname, path); err != nil {
if err := os.Symlink(hdr.Linkname, dstPath); err != nil {
return err
}
@@ -504,12 +520,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
if chownOpts == nil {
chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid}
}
if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil {
if err := os.Lchown(dstPath, chownOpts.UID, chownOpts.GID); err != nil {
var msg string
if inUserns && errors.Is(err, syscall.EINVAL) {
msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)"
}
return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err)
return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", dstPath, hdr.Uid, hdr.Gid, msg, err)
}
}
@@ -519,7 +535,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
if !ok {
continue
}
if err := lsetxattr(path, xattr, []byte(value), 0); err != nil {
if err := lsetxattr(dstPath, xattr, []byte(value), 0); err != nil {
if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) {
// EPERM occurs if modifying xattrs is not allowed. This can
// happen when running in userns with restrictions (ChromeOS).
@@ -538,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
// 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
}
aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime))
mTime := boundTime(hdr.ModTime)
// 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 {
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 {
return err
}
}
} 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 {
default:
// All other file types follow symlinks.
if err := chtimes(dstPath, aTime, mTime); err != nil {
return err
}
}
return nil
}
// Tar creates an archive from the directory at `path`, and returns it as a
// Tar creates an archive from the directory at `srcPath`, and returns it as a
// stream of bytes.
func Tar(path string, comp compression.Compression) (io.ReadCloser, error) {
return TarWithOptions(path, &TarOptions{Compression: comp})
func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) {
return TarWithOptions(srcPath, &TarOptions{Compression: comp})
}
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
// TarWithOptions creates an archive from the directory at `srcPath`, only including files whose relative
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
tb, err := NewTarballer(srcPath, options)
@@ -805,6 +825,9 @@ 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
@@ -846,8 +869,8 @@ loop:
}
// #nosec G305 -- The joined path is checked for path traversal.
path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path)
dstPath := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, dstPath)
if err != nil {
return err
}
@@ -855,21 +878,21 @@ loop:
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 layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer).
if fi, err := os.Lstat(path); err == nil {
if fi, err := os.Lstat(dstPath); err == nil {
if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir {
// If NoOverwriteDirNonDir is true then we cannot replace
// an existing directory with a non-directory from the archive.
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest)
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", dstPath, dest)
}
if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir {
// If NoOverwriteDirNonDir is true then we cannot replace
// an existing non-directory with a directory from the archive.
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest)
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", dstPath, dest)
}
if fi.IsDir() && hdr.Name == "." {
@@ -877,7 +900,7 @@ loop:
}
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
if err := os.RemoveAll(path); err != nil {
if err := os.RemoveAll(dstPath); err != nil {
return err
}
}
@@ -888,7 +911,7 @@ loop:
}
if whiteoutConverter != nil {
writeFile, err := whiteoutConverter.ConvertRead(hdr, path)
writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath)
if err != nil {
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
}
@@ -910,9 +933,8 @@ loop:
for _, hdr := range dirs {
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name)
if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
dstPath := filepath.Join(dest, hdr.Name)
if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
return err
}
}
@@ -923,16 +945,15 @@ loop:
// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is
// defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus
// we most both create them and choose metadata like permissions.
//
// The caller should have performed filepath.Clean(hdr.Name), so hdr.Name will now be in the filepath format for the OS
// on which the daemon is running. This precondition is required because this function assumes a OS-specific path
// separator when checking that a path is not the root.
func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error {
// Not the root directory, ensure that the parent directory exists
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
// For non-directory entries, ensure that the parent directory exists.
if hdr.Typeflag != tar.TypeDir {
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
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.
@@ -974,9 +995,6 @@ 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 {
@@ -984,7 +1002,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
if err != nil {
return err
}
defer decompressedArchive.Close()
defer func() { _ = decompressedArchive.Close() }()
r = decompressedArchive
}
@@ -998,7 +1016,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
if err != nil {
return err
}
defer archive.Close()
defer func() { _ = archive.Close() }()
return archiver.Untar(archive, dst, &TarOptions{
IDMap: archiver.IDMapping,
})
@@ -1010,7 +1028,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
if err != nil {
return err
}
defer archive.Close()
defer func() { _ = archive.Close() }()
return archiver.Untar(archive, dst, &TarOptions{
IDMap: archiver.IDMapping,
})
@@ -1070,13 +1088,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
defer close(errC)
errC <- func() error {
defer w.Close()
defer func() { _ = w.Close() }()
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
defer func() { _ = srcF.Close() }()
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
if err != nil {
@@ -1087,14 +1105,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 = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
hdr.Mode = chmodTarEntry(hdr.Mode)
if err := remapIDs(archiver.IDMapping, hdr); err != nil {
return err
}
tw := tar.NewWriter(w)
defer tw.Close()
defer func() { _ = tw.Close() }()
if err := tw.WriteHeader(hdr); err != nil {
return err
}
@@ -1112,7 +1130,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
}

View File

@@ -4,6 +4,7 @@ import (
"archive/tar"
"fmt"
"os"
"path"
"path/filepath"
"strings"
@@ -13,36 +14,43 @@ import (
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
if format == OverlayWhiteoutFormat {
return overlayWhiteoutConverter{}
return newOverlayWhiteoutConverter()
}
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
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
// we just rename the file and make it normal
dir, filename := filepath.Split(hdr.Name)
hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename)
dir, filename := path.Split(hdr.Name)
hdr.Name = path.Join(dir, WhiteoutPrefix+filename)
hdr.Mode = 0o600
hdr.Typeflag = tar.TypeReg
hdr.Size = 0
}
if fi.Mode()&os.ModeDir == 0 {
if !fi.IsDir() {
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
return nil, nil
}
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(path, opaqueXattrName)
opaque, err := lgetxattr(filePath, c.opaqueXattr)
if err != nil {
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
return nil, nil
}
delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName)
delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr)
// 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: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
Size: 0,
Uid: hdr.Uid,
Uname: hdr.Uname,
@@ -68,40 +76,54 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
}, nil
}
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
base := filepath.Base(path)
dir := filepath.Dir(path)
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) {
base := filepath.Base(filePath)
dir := filepath.Dir(filePath)
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
if base == WhiteoutOpaqueDir {
opaqueXattrName := "trusted.overlay.opaque"
if userns.RunningInUserNS() {
opaqueXattrName = "user.overlay.opaque"
switch base {
case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..":
return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name)
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)
}
// Don't write the whiteout file itself.
return false, nil
err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0)
if err != nil {
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err)
default:
originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix)
if !ok {
// Regular file.
return true, nil
}
// 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):]
// If a file was deleted, and we are using overlay, we need to create a character device.
originalPath := filepath.Join(dir, originalBase)
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
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 {
return false, 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
}
}
// don't write the file itself
// Don't write the whiteout file itself.
return false, nil
}
return true, nil
}

View File

@@ -5,6 +5,8 @@ package archive
import (
"archive/tar"
"errors"
"fmt"
"math"
"os"
"path/filepath"
"strings"
@@ -13,6 +15,8 @@ import (
"golang.org/x/sys/unix"
)
var errInvalidArchive = errors.New("invalid archive")
// addLongPathPrefix adds the Windows long path prefix to the path provided if
// it does not already have it. It is a no-op on platforms other than Windows.
func addLongPathPrefix(srcPath string) string {
@@ -29,20 +33,19 @@ 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(perm os.FileMode) os.FileMode {
return perm // noop for unix as golang APIs provide perm bits correctly
func chmodTarEntry(mode int64) int64 {
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)
if !ok {
// FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897
return 0, nil
return 0, fmt.Errorf("unexpected stat type %T", stat)
}
return s.Ino, nil
}
func getFileUIDGID(stat interface{}) (int, int, error) {
func getFileUIDGID(stat any) (int, int, error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
@@ -56,7 +59,7 @@ func getFileUIDGID(stat interface{}) (int, int, error) {
//
// Creating device nodes is not supported when running in a user namespace,
// produces a [syscall.EPERM] in most cases.
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error {
mode := uint32(hdr.Mode & 0o7777)
switch hdr.Typeflag {
case tar.TypeBlock:
@@ -67,18 +70,28 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
mode |= unix.S_IFIFO
}
return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
// Devmajor and Devminor come straight from the (untrusted) tar header as
// int64, but Mkdev only takes uint32. Casting a value that does not fit
// silently truncates it, so the node created on disk would carry a
// different major/minor than the header declares. Reject those instead of
// creating a mismatched device.
if hdr.Devmajor < 0 || hdr.Devmajor > math.MaxUint32 ||
hdr.Devminor < 0 || hdr.Devminor > math.MaxUint32 {
return fmt.Errorf("device number %d:%d for %q out of range: %w", hdr.Devmajor, hdr.Devminor, hdr.Name, errInvalidArchive)
}
return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
}
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error {
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
return err
}
}

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
// 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.
perm &= 0o755
mode &= 0o755
// 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
return 0, nil
}
@@ -56,7 +56,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
return nil
}
func getFileUIDGID(stat interface{}) (int, int, error) {
func getFileUIDGID(stat any) (int, int, error) {
// no notion of file ownership mapping yet on Windows
return 0, 0, nil
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"io/fs"
"maps"
"os"
"path/filepath"
"sort"
@@ -217,8 +218,8 @@ func (info *FileInfo) LookUp(path string) *FileInfo {
return info
}
pathElements := strings.Split(path, string(os.PathSeparator))
for _, elem := range pathElements {
pathElements := strings.SplitSeq(path, string(os.PathSeparator))
for elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
@@ -256,9 +257,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
maps.Copy(oldChildren, oldInfo.children)
}
for name, newChild := range info.children {
@@ -401,7 +400,7 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io
whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
timestamp := time.Now()
hdr := &tar.Header{
Name: whiteOut[1:],
Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"),
Size: 0,
ModTime: timestamp,
AccessTime: timestamp,
@@ -411,9 +410,10 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io
log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err)
}
} else {
path := filepath.Join(dir, change.Path)
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err)
srcPath := filepath.Join(dir, change.Path)
archivePath := strings.TrimPrefix(filepath.ToSlash(change.Path), "/")
if err := ta.addTarFile(srcPath, archivePath); err != nil {
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", srcPath, err)
}
}
}

View File

@@ -265,7 +265,7 @@ func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno)
}
func clen(n []byte) int {
for i := 0; i < len(n); i++ {
for i := range n {
if n[i] == 0 {
return i
}

View File

@@ -26,7 +26,7 @@ func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, err
}()
// block until both routines have returned
for i := 0; i < 2; i++ {
for range 2 {
if err := <-errs; err != nil {
return nil, nil, err
}

View File

@@ -59,9 +59,6 @@ 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.

View File

@@ -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()
}
}()

View File

@@ -31,9 +31,6 @@ 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)
}

View File

@@ -66,7 +66,7 @@ type nopWriteCloser struct {
func (nopWriteCloser) Close() error { return nil }
var bufioReader32KPool = &sync.Pool{
New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) },
New: func() any { return bufio.NewReaderSize(nil, 32*1024) },
}
type bufferedReader struct {
@@ -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)
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.

View File

@@ -22,7 +22,7 @@ var (
)
var copyPool = sync.Pool{
New: func() interface{} { s := make([]byte, 32*1024); return &s },
New: func() any { s := make([]byte, 32*1024); return &s },
}
func copyWithBuffer(dst io.Writer, src io.Reader) error {
@@ -319,7 +319,10 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir
// RebaseArchiveEntries rewrites the given srcContent archive replacing
// an occurrence of oldBase with newBase at the beginning of entry names.
func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser {
if oldBase == string(os.PathSeparator) {
oldBase = filepath.ToSlash(oldBase)
newBase = filepath.ToSlash(newBase)
if oldBase == "/" {
// If oldBase specifies the root directory, use an empty string as
// oldBase instead so that newBase doesn't replace the path separator
// that all paths will start with.
@@ -336,12 +339,12 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
hdr, err := srcTar.Next()
if errors.Is(err, io.EOF) {
// Signals end of archive.
rebasedTar.Close()
w.Close()
_ = rebasedTar.Close()
_ = w.Close()
return
}
if err != nil {
w.CloseWithError(err)
_ = w.CloseWithError(err)
return
}
@@ -359,7 +362,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
}
if err = rebasedTar.WriteHeader(hdr); err != nil {
w.CloseWithError(err)
_ = w.CloseWithError(err)
return
}
@@ -374,7 +377,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
// not be vulnerable to this code consuming memory.
//nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec)
if _, err = io.Copy(rebasedTar, srcTar); err != nil {
w.CloseWithError(err)
_ = w.CloseWithError(err)
return
}
}
@@ -408,7 +411,7 @@ func CopyResource(srcPath, dstPath string, followLink bool) error {
if err != nil {
return err
}
defer content.Close()
defer func() { _ = content.Close() }()
return CopyTo(content, srcInfo, dstPath)
}
@@ -427,14 +430,12 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error {
if err != nil {
return err
}
defer copyArchive.Close()
defer func() { _ = copyArchive.Close() }()
options := &TarOptions{
return Untar(copyArchive, dstDir, &TarOptions{
NoLchown: true,
NoOverwriteDirNonDir: true,
}
return Untar(copyArchive, dstDir, options)
})
}
// ResolveHostSourcePath decides real path need to be copied with parameters such as

View File

@@ -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))
return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
}

View File

@@ -28,9 +28,6 @@ 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)
@@ -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.
path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path)
dstPath := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, dstPath)
if err != nil {
return 0, err
}
@@ -112,10 +109,10 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
}
base := filepath.Base(path)
base := filepath.Base(dstPath)
if strings.HasPrefix(base, WhiteoutPrefix) {
dir := filepath.Dir(path)
dir := filepath.Dir(dstPath)
if base == WhiteoutOpaqueDir {
_, err := os.Lstat(dir)
if err != nil {
@@ -132,7 +129,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return nil
}
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
})
@@ -147,13 +144,13 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
}
}
} 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 layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer).
if fi, err := os.Lstat(path); err == nil {
if fi, err := os.Lstat(dstPath); err == nil {
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
if err := os.RemoveAll(path); err != nil {
if err := os.RemoveAll(dstPath); err != nil {
return 0, err
}
}
@@ -182,7 +179,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return 0, err
}
if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil {
if err := createTarFile(dstPath, dest, srcHdr, srcData, options); err != nil {
return 0, err
}
@@ -191,14 +188,14 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if hdr.Typeflag == tar.TypeDir {
dirs = append(dirs, hdr)
}
unpackedPaths[path] = struct{}{}
unpackedPaths[dstPath] = struct{}{}
}
}
for _, hdr := range dirs {
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name)
if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
dstPath := filepath.Join(dest, hdr.Name)
if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil {
return 0, err
}
}

View File

@@ -32,7 +32,7 @@ func (fi nosysFileInfo) Gname() (string, error) {
return "", nil
}
func (fi nosysFileInfo) Sys() interface{} {
func (fi nosysFileInfo) Sys() any {
// A Sys value of type *tar.Header is safe as it is system-independent.
// The tar.FileInfoHeader function copies the fields into the returned
// header without performing any OS lookups.

View File

@@ -36,10 +36,11 @@ 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 {
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
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))
}
return nil

View File

@@ -13,26 +13,26 @@ import (
// lgetxattr retrieves the value of the extended attribute identified by attr
// and associated with the given path in the file system.
// It returns a nil slice and nil error if the xattr is not set.
func lgetxattr(path string, attr string) ([]byte, error) {
func lgetxattr(filePath string, attr string) ([]byte, error) {
// Start with a 128 length byte array
dest := make([]byte, 128)
sz, err := unix.Lgetxattr(path, attr, dest)
sz, err := unix.Lgetxattr(filePath, attr, dest)
for errors.Is(err, unix.ERANGE) {
// Buffer too small, use zero-sized buffer to get the actual size
sz, err = unix.Lgetxattr(path, attr, []byte{})
sz, err = unix.Lgetxattr(filePath, attr, []byte{})
if err != nil {
return nil, wrapPathError("lgetxattr", path, attr, err)
return nil, wrapPathError("lgetxattr", filePath, attr, err)
}
dest = make([]byte, sz)
sz, err = unix.Lgetxattr(path, attr, dest)
sz, err = unix.Lgetxattr(filePath, attr, dest)
}
if err != nil {
if errors.Is(err, noattr) {
return nil, nil
}
return nil, wrapPathError("lgetxattr", path, attr, err)
return nil, wrapPathError("lgetxattr", filePath, attr, err)
}
return dest[:sz], nil
@@ -40,13 +40,13 @@ func lgetxattr(path string, attr string) ([]byte, error) {
// lsetxattr sets the value of the extended attribute identified by attr
// and associated with the given path in the file system.
func lsetxattr(path string, attr string, data []byte, flags int) error {
return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags))
func lsetxattr(filePath string, attr string, data []byte, flags int) error {
return wrapPathError("lsetxattr", filePath, attr, unix.Lsetxattr(filePath, attr, data, flags))
}
func wrapPathError(op, path, attr string, err error) error {
func wrapPathError(op, filePath, attr string, err error) error {
if err == nil {
return nil
}
return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)}
return &fs.PathError{Op: op, Path: filePath, Err: fmt.Errorf("xattr %q: %w", attr, err)}
}

4
vendor/modules.txt vendored
View File

@@ -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.0
## explicit; go 1.23.0
# github.com/moby/go-archive v0.2.1
## explicit; go 1.25
github.com/moby/go-archive
github.com/moby/go-archive/chrootarchive
github.com/moby/go-archive/compression