From 467d8c17d0cdfe8475047a6973a9179685562f8d Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:15:05 +0200 Subject: [PATCH 1/2] replace go-archive with containerd archive apply This removes github.com/moby/go-archive from Dockerfile ADD extraction and archive probing. Extraction now uses containerd's archive.Apply, with a small local decompression helper for Docker-compatible formats while preserving whiteout and ownership mapping behavior. Tests cover symlink and hardlink escape attempts around the extraction root so this does not regress the confinement expected from the old chrootarchive path. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- frontend/dockerfile/dockerfile2llb/epoch.go | 4 +- frontend/dockerui/context.go | 10 +- go.mod | 2 - go.sum | 8 +- solver/llbsolver/file/backend.go | 2 +- solver/llbsolver/file/unpack.go | 53 +- solver/llbsolver/file/unpack_test.go | 56 + solver/llbsolver/file/unpack_unix.go | 9 + solver/llbsolver/file/unpack_unix_test.go | 123 ++ solver/llbsolver/file/unpack_windows.go | 11 + solver/llbsolver/file/unpack_windows_test.go | 33 + util/archiveutil/compression.go | 100 ++ util/archiveutil/compression_test.go | 97 ++ .../github.com/moby/go-archive/.gitattributes | 2 - vendor/github.com/moby/go-archive/.gitignore | 1 - .../github.com/moby/go-archive/.golangci.yml | 33 - vendor/github.com/moby/go-archive/LICENSE | 202 --- vendor/github.com/moby/go-archive/archive.go | 1129 ----------------- .../moby/go-archive/archive_linux.go | 107 -- .../moby/go-archive/archive_other.go | 7 - .../moby/go-archive/archive_unix.go | 86 -- .../moby/go-archive/archive_windows.go | 62 - vendor/github.com/moby/go-archive/changes.go | 430 ------- .../moby/go-archive/changes_linux.go | 274 ---- .../moby/go-archive/changes_other.go | 95 -- .../moby/go-archive/changes_unix.go | 43 - .../moby/go-archive/changes_windows.go | 33 - .../moby/go-archive/chrootarchive/archive.go | 98 -- .../go-archive/chrootarchive/archive_linux.go | 54 - .../go-archive/chrootarchive/archive_unix.go | 60 - .../chrootarchive/archive_unix_nolinux.go | 226 ---- .../chrootarchive/archive_windows.go | 40 - .../go-archive/chrootarchive/chroot_linux.go | 35 - .../moby/go-archive/chrootarchive/diff.go | 23 - .../go-archive/chrootarchive/diff_unix.go | 39 - .../go-archive/chrootarchive/diff_windows.go | 34 - .../go-archive/compression/compression.go | 263 ---- .../compression/compression_detect.go | 65 - vendor/github.com/moby/go-archive/copy.go | 496 -------- .../github.com/moby/go-archive/copy_unix.go | 11 - .../moby/go-archive/copy_windows.go | 9 - .../github.com/moby/go-archive/dev_freebsd.go | 9 - vendor/github.com/moby/go-archive/dev_unix.go | 9 - vendor/github.com/moby/go-archive/diff.go | 261 ---- .../github.com/moby/go-archive/diff_unix.go | 21 - .../moby/go-archive/diff_windows.go | 6 - .../internal/mounttree/switchroot_linux.go | 94 -- .../internal/unshare/unshare_linux.go | 173 --- vendor/github.com/moby/go-archive/path.go | 20 - .../github.com/moby/go-archive/path_unix.go | 9 - .../moby/go-archive/path_windows.go | 22 - .../moby/go-archive/tarheader/tarheader.go | 67 - .../go-archive/tarheader/tarheader_unix.go | 46 - .../go-archive/tarheader/tarheader_windows.go | 12 - vendor/github.com/moby/go-archive/time.go | 38 - .../moby/go-archive/time_nonwindows.go | 41 - .../moby/go-archive/time_windows.go | 32 - .../github.com/moby/go-archive/whiteouts.go | 23 - vendor/github.com/moby/go-archive/wrap.go | 59 - .../moby/go-archive/xattr_supported.go | 52 - .../moby/go-archive/xattr_supported_linux.go | 5 - .../moby/go-archive/xattr_supported_unix.go | 7 - .../moby/go-archive/xattr_unsupported.go | 11 - vendor/github.com/moby/sys/mount/LICENSE | 202 --- vendor/github.com/moby/sys/mount/doc.go | 4 - vendor/github.com/moby/sys/mount/flags_bsd.go | 45 - .../github.com/moby/sys/mount/flags_linux.go | 87 -- .../github.com/moby/sys/mount/flags_unix.go | 139 -- .../github.com/moby/sys/mount/mount_errors.go | 46 - .../github.com/moby/sys/mount/mount_unix.go | 88 -- .../moby/sys/mount/mounter_freebsd.go | 61 - .../moby/sys/mount/mounter_linux.go | 72 -- .../moby/sys/mount/mounter_openbsd.go | 77 -- .../moby/sys/mount/mounter_unsupported.go | 7 - .../moby/sys/mount/sharedsubtree_linux.go | 73 -- vendor/modules.txt | 11 - 76 files changed, 477 insertions(+), 5917 deletions(-) create mode 100644 solver/llbsolver/file/unpack_test.go create mode 100644 solver/llbsolver/file/unpack_unix.go create mode 100644 solver/llbsolver/file/unpack_unix_test.go create mode 100644 solver/llbsolver/file/unpack_windows.go create mode 100644 solver/llbsolver/file/unpack_windows_test.go create mode 100644 util/archiveutil/compression.go create mode 100644 util/archiveutil/compression_test.go delete mode 100644 vendor/github.com/moby/go-archive/.gitattributes delete mode 100644 vendor/github.com/moby/go-archive/.gitignore delete mode 100644 vendor/github.com/moby/go-archive/.golangci.yml delete mode 100644 vendor/github.com/moby/go-archive/LICENSE delete mode 100644 vendor/github.com/moby/go-archive/archive.go delete mode 100644 vendor/github.com/moby/go-archive/archive_linux.go delete mode 100644 vendor/github.com/moby/go-archive/archive_other.go delete mode 100644 vendor/github.com/moby/go-archive/archive_unix.go delete mode 100644 vendor/github.com/moby/go-archive/archive_windows.go delete mode 100644 vendor/github.com/moby/go-archive/changes.go delete mode 100644 vendor/github.com/moby/go-archive/changes_linux.go delete mode 100644 vendor/github.com/moby/go-archive/changes_other.go delete mode 100644 vendor/github.com/moby/go-archive/changes_unix.go delete mode 100644 vendor/github.com/moby/go-archive/changes_windows.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/archive.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/diff.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go delete mode 100644 vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go delete mode 100644 vendor/github.com/moby/go-archive/compression/compression.go delete mode 100644 vendor/github.com/moby/go-archive/compression/compression_detect.go delete mode 100644 vendor/github.com/moby/go-archive/copy.go delete mode 100644 vendor/github.com/moby/go-archive/copy_unix.go delete mode 100644 vendor/github.com/moby/go-archive/copy_windows.go delete mode 100644 vendor/github.com/moby/go-archive/dev_freebsd.go delete mode 100644 vendor/github.com/moby/go-archive/dev_unix.go delete mode 100644 vendor/github.com/moby/go-archive/diff.go delete mode 100644 vendor/github.com/moby/go-archive/diff_unix.go delete mode 100644 vendor/github.com/moby/go-archive/diff_windows.go delete mode 100644 vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go delete mode 100644 vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go delete mode 100644 vendor/github.com/moby/go-archive/path.go delete mode 100644 vendor/github.com/moby/go-archive/path_unix.go delete mode 100644 vendor/github.com/moby/go-archive/path_windows.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go delete mode 100644 vendor/github.com/moby/go-archive/time.go delete mode 100644 vendor/github.com/moby/go-archive/time_nonwindows.go delete mode 100644 vendor/github.com/moby/go-archive/time_windows.go delete mode 100644 vendor/github.com/moby/go-archive/whiteouts.go delete mode 100644 vendor/github.com/moby/go-archive/wrap.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported_linux.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported_unix.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_unsupported.go delete mode 100644 vendor/github.com/moby/sys/mount/LICENSE delete mode 100644 vendor/github.com/moby/sys/mount/doc.go delete mode 100644 vendor/github.com/moby/sys/mount/flags_bsd.go delete mode 100644 vendor/github.com/moby/sys/mount/flags_linux.go delete mode 100644 vendor/github.com/moby/sys/mount/flags_unix.go delete mode 100644 vendor/github.com/moby/sys/mount/mount_errors.go delete mode 100644 vendor/github.com/moby/sys/mount/mount_unix.go delete mode 100644 vendor/github.com/moby/sys/mount/mounter_freebsd.go delete mode 100644 vendor/github.com/moby/sys/mount/mounter_linux.go delete mode 100644 vendor/github.com/moby/sys/mount/mounter_openbsd.go delete mode 100644 vendor/github.com/moby/sys/mount/mounter_unsupported.go delete mode 100644 vendor/github.com/moby/sys/mount/sharedsubtree_linux.go diff --git a/frontend/dockerfile/dockerfile2llb/epoch.go b/frontend/dockerfile/dockerfile2llb/epoch.go index 03e7d667d310..5cef1a8bcd63 100644 --- a/frontend/dockerfile/dockerfile2llb/epoch.go +++ b/frontend/dockerfile/dockerfile2llb/epoch.go @@ -21,8 +21,8 @@ import ( "github.com/moby/buildkit/frontend/dockerui" gwclient "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/buildkit/util/gitutil/gitobject" - archivecompression "github.com/moby/go-archive/compression" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -348,7 +348,7 @@ func archiveMaxTimeFromRef(ctx context.Context, ref gwclient.Reference, filename if err != nil { return nil, err } - rc, err := archivecompression.DecompressStream(bytes.NewReader(dt)) + rc, err := archiveutil.DecompressStream(bytes.NewReader(dt)) if err != nil { if allowNonArchive { return nil, nil diff --git a/frontend/dockerui/context.go b/frontend/dockerui/context.go index 6fb3ebd1be60..78b47ab22a9d 100644 --- a/frontend/dockerui/context.go +++ b/frontend/dockerui/context.go @@ -19,8 +19,8 @@ import ( "github.com/moby/buildkit/frontend/gateway/client" gwpb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/buildkit/util/gitutil/gitobject" - archivecompression "github.com/moby/go-archive/compression" "github.com/pkg/errors" ) @@ -224,7 +224,7 @@ func archiveMaxTimeFromHTTPArchive(ctx context.Context, bctx *buildContext) (*ti if err != nil { return nil, err } - rc, err := archivecompression.DecompressStream(bytes.NewReader(dt)) + rc, err := archiveutil.DecompressStream(bytes.NewReader(dt)) if err != nil { return nil, err } @@ -339,11 +339,9 @@ func isArchive(header []byte) bool { {0x42, 0x5A, 0x68}, // bzip2 {0x1F, 0x8B, 0x08}, // gzip {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz + {0x28, 0xB5, 0x2F, 0xFD}, // zstd } { - if len(header) < len(m) { - continue - } - if bytes.Equal(m, header[:len(m)]) { + if bytes.HasPrefix(header, m) { return true } } diff --git a/go.mod b/go.mod index 89f3be5b0a0d..c90cf444efb6 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,6 @@ 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/locker v1.0.1 github.com/moby/patternmatcher v0.6.1 github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4 @@ -195,7 +194,6 @@ require ( github.com/in-toto/attestation v1.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect - github.com/moby/sys/mount v0.3.5 // indirect github.com/moby/sys/sequential v0.7.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect diff --git a/go.sum b/go.sum index 56fe52a74274..c10a54968f39 100644 --- a/go.sum +++ b/go.sum @@ -418,8 +418,6 @@ 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/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= @@ -430,8 +428,6 @@ github.com/moby/profiles/seccomp v0.2.3 h1:nrHNSiECQQvq4WjgceCUgJXIXUJBIswVQ133k github.com/moby/profiles/seccomp v0.2.3/go.mod h1:8m3qkkWZXrRsMqlUUN2zyccnYmBf3EAdQYPMVJ3NBhk= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= -github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00= -github.com/moby/sys/mount v0.3.5/go.mod h1:WUQDO+/uCiCIkIztx8SrwIDVn2dtMFRBebRhpDFT71M= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/reexec v0.1.0 h1:RrBi8e0EBTLEgfruBOFcxtElzRGTEUkeIFaVXgU7wok= @@ -779,8 +775,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= diff --git a/solver/llbsolver/file/backend.go b/solver/llbsolver/file/backend.go index be431a4f7c94..b560e89155e0 100644 --- a/solver/llbsolver/file/backend.go +++ b/solver/llbsolver/file/backend.go @@ -255,7 +255,7 @@ func docopy(ctx context.Context, src, dest string, action *pb.FileActionCopy, u for _, s := range m { if action.AttemptUnpackDockerCompatibility { - if ok, err := unpack(src, s, dest, destPath, ch, u, timestampToTime(action.Timestamp), idmap); err != nil { + if ok, err := unpack(ctx, src, s, dest, destPath, ch, u, timestampToTime(action.Timestamp), idmap); err != nil { return errors.WithStack(err) } else if ok { continue diff --git a/solver/llbsolver/file/unpack.go b/solver/llbsolver/file/unpack.go index de27705004c1..2fc536a8a28e 100644 --- a/solver/llbsolver/file/unpack.go +++ b/solver/llbsolver/file/unpack.go @@ -2,18 +2,18 @@ package file import ( "archive/tar" + "context" "os" "time" + "github.com/containerd/containerd/v2/pkg/archive" "github.com/containerd/continuity/fs" - "github.com/moby/go-archive" - "github.com/moby/go-archive/chrootarchive" - "github.com/moby/go-archive/compression" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/sys/user" copy "github.com/tonistiigi/fsutil/copy" ) -func unpack(srcRoot string, src string, destRoot string, dest string, ch copy.Chowner, u *copy.User, tm *time.Time, idmap *user.IdentityMapping) (bool, error) { +func unpack(ctx context.Context, srcRoot string, src string, destRoot string, dest string, ch copy.Chowner, u *copy.User, tm *time.Time, idmap *user.IdentityMapping) (bool, error) { src, err := fs.RootPath(srcRoot, src) if err != nil { return false, err @@ -36,19 +36,44 @@ func unpack(srcRoot string, src string, destRoot string, dest string, ch copy.Ch } defer file.Close() - opts := &archive.TarOptions{ - BestEffortXattrs: true, + rdr, err := archiveutil.DecompressStream(file) + if err != nil { + return false, err } - if idmap != nil { - opts.IDMap = *idmap + defer rdr.Close() + + opts := []archive.ApplyOpt{ + // Disable containerd's image-layer whiteout conversion for Dockerfile ADD + // archives, where .wh.* entries should be extracted as files. + archive.WithConvertWhiteout(func(_ *tar.Header, _ string) (bool, error) { + return true, nil + }), + archive.WithFilter(ownerMapper(u, idmap)), } - if u != nil { - opts.ChownOpts = &archive.ChownOpts{ - UID: u.UID, - GID: u.GID, + opts = append(opts, unpackPlatformApplyOpts()...) + + _, err = archive.Apply(ctx, dest, rdr, opts...) + return true, err +} + +func ownerMapper(u *copy.User, idmap *user.IdentityMapping) archive.Filter { + return func(hdr *tar.Header) (bool, error) { + uid, gid := hdr.Uid, hdr.Gid + // Match go-archive behavior: remap archive header IDs first, then let + // explicit --chown values override the header ownership. + if idmap != nil { + var err error + uid, gid, err = idmap.ToHost(uid, gid) + if err != nil { + return false, err + } + } + if u != nil { + uid, gid = u.UID, u.GID } + hdr.Uid, hdr.Gid = uid, gid + return true, nil } - return true, chrootarchive.Untar(file, dest, opts) } func isArchivePath(path string) bool { @@ -64,7 +89,7 @@ func isArchivePath(path string) bool { return false } defer file.Close() - rdr, err := compression.DecompressStream(file) + rdr, err := archiveutil.DecompressStream(file) if err != nil { return false } diff --git a/solver/llbsolver/file/unpack_test.go b/solver/llbsolver/file/unpack_test.go new file mode 100644 index 000000000000..4eddefcd60f6 --- /dev/null +++ b/solver/llbsolver/file/unpack_test.go @@ -0,0 +1,56 @@ +package file + +import ( + "archive/tar" + "testing" + + "github.com/moby/sys/user" + "github.com/stretchr/testify/require" + copy "github.com/tonistiigi/fsutil/copy" +) + +func TestOwnerMapperMapsArchiveHeader(t *testing.T) { + t.Parallel() + + idmap := &user.IdentityMapping{ + UIDMaps: []user.IDMap{{ID: 0, ParentID: 1000, Count: 10}}, + GIDMaps: []user.IDMap{{ID: 0, ParentID: 2000, Count: 10}}, + } + hdr := &tar.Header{Uid: 2, Gid: 3} + + ok, err := ownerMapper(nil, idmap)(hdr) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, 1002, hdr.Uid) + require.Equal(t, 2003, hdr.Gid) +} + +func TestOwnerMapperChownOverridesArchiveHeader(t *testing.T) { + t.Parallel() + + idmap := &user.IdentityMapping{ + UIDMaps: []user.IDMap{{ID: 0, ParentID: 1000, Count: 10}}, + GIDMaps: []user.IDMap{{ID: 0, ParentID: 2000, Count: 10}}, + } + hdr := &tar.Header{Uid: 2, Gid: 3} + + ok, err := ownerMapper(©.User{UID: 5, GID: 6}, idmap)(hdr) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, 5, hdr.Uid) + require.Equal(t, 6, hdr.Gid) +} + +func writeTarFile(t *testing.T, tw *tar.Writer, name, content string) { + t.Helper() + + err := tw.WriteHeader(&tar.Header{ + Name: name, + Typeflag: tar.TypeReg, + Mode: 0o644, + Size: int64(len(content)), + }) + require.NoError(t, err) + _, err = tw.Write([]byte(content)) + require.NoError(t, err) +} diff --git a/solver/llbsolver/file/unpack_unix.go b/solver/llbsolver/file/unpack_unix.go new file mode 100644 index 000000000000..275e97ec6bdb --- /dev/null +++ b/solver/llbsolver/file/unpack_unix.go @@ -0,0 +1,9 @@ +//go:build !windows + +package file + +import "github.com/containerd/containerd/v2/pkg/archive" + +func unpackPlatformApplyOpts() []archive.ApplyOpt { + return nil +} diff --git a/solver/llbsolver/file/unpack_unix_test.go b/solver/llbsolver/file/unpack_unix_test.go new file mode 100644 index 000000000000..3969a081bfa0 --- /dev/null +++ b/solver/llbsolver/file/unpack_unix_test.go @@ -0,0 +1,123 @@ +//go:build !windows + +package file + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/containerd/containerd/v2/pkg/archive" + "github.com/stretchr/testify/require" +) + +func TestUnpackPreservesWhiteoutFiles(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("archive apply preserves tar ownership and requires root") + } + + srcRoot := t.TempDir() + destRoot := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "victim", "victim") + writeTarFile(t, tw, ".wh.victim", "whiteout") + require.NoError(t, tw.Close()) + + require.NoError(t, os.WriteFile(filepath.Join(srcRoot, "archive.tar"), buf.Bytes(), 0o600)) + + ok, err := unpack(t.Context(), srcRoot, "archive.tar", destRoot, "/", nil, nil, nil, nil) + require.NoError(t, err) + require.True(t, ok) + + dt, err := os.ReadFile(filepath.Join(destRoot, "victim")) + require.NoError(t, err) + require.Equal(t, "victim", string(dt)) + + dt, err = os.ReadFile(filepath.Join(destRoot, ".wh.victim")) + require.NoError(t, err) + require.Equal(t, "whiteout", string(dt)) +} + +func TestApplyDoesNotWriteThroughArchiveSymlink(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "escape", + Typeflag: tar.TypeSymlink, + Linkname: filepath.ToSlash(outside), + Mode: 0o777, + })) + writeTarFile(t, tw, "escape/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "archive-created symlink escaped destination") +} + +func TestApplyDoesNotWriteThroughPreexistingSymlink(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + require.NoError(t, os.Symlink(outside, filepath.Join(dest, "escape"))) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "escape/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "preexisting symlink escaped destination") +} + +func TestApplyDoesNotHardlinkOutsideDestination(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(outside, "target"), []byte("target"), 0o644)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "hardlink", + Typeflag: tar.TypeLink, + Linkname: filepath.ToSlash(filepath.Join(outside, "target")), + Mode: 0o644, + })) + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(outside, "target")) + require.NoError(t, err) + require.Equal(t, "target", string(dt)) +} + +func applyArchiveNoSameOwner(t *testing.T, dest string, dt []byte) error { + t.Helper() + + _, err := archive.Apply(t.Context(), dest, bytes.NewReader(dt), + archive.WithConvertWhiteout(func(_ *tar.Header, _ string) (bool, error) { + return true, nil + }), + archive.WithNoSameOwner(), + ) + return err +} diff --git a/solver/llbsolver/file/unpack_windows.go b/solver/llbsolver/file/unpack_windows.go new file mode 100644 index 000000000000..03ac67eeba69 --- /dev/null +++ b/solver/llbsolver/file/unpack_windows.go @@ -0,0 +1,11 @@ +//go:build windows + +package file + +import "github.com/containerd/containerd/v2/pkg/archive" + +func unpackPlatformApplyOpts() []archive.ApplyOpt { + // Windows does not support lchown. The old go-archive path skipped ownership + // changes on Windows while still extracting the archive contents. + return []archive.ApplyOpt{archive.WithNoSameOwner()} +} diff --git a/solver/llbsolver/file/unpack_windows_test.go b/solver/llbsolver/file/unpack_windows_test.go new file mode 100644 index 000000000000..68a2365efd69 --- /dev/null +++ b/solver/llbsolver/file/unpack_windows_test.go @@ -0,0 +1,33 @@ +//go:build windows + +package file + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnpackSkipsSameOwnerOnWindows(t *testing.T) { + srcRoot := t.TempDir() + destRoot := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "foo", "content") + require.NoError(t, tw.Close()) + + require.NoError(t, os.WriteFile(filepath.Join(srcRoot, "archive.tar"), buf.Bytes(), 0o600)) + + ok, err := unpack(t.Context(), srcRoot, "archive.tar", destRoot, "/", nil, nil, nil, nil) + require.NoError(t, err) + require.True(t, ok) + + dt, err := os.ReadFile(filepath.Join(destRoot, "foo")) + require.NoError(t, err) + require.Equal(t, "content", string(dt)) +} diff --git a/util/archiveutil/compression.go b/util/archiveutil/compression.go new file mode 100644 index 000000000000..96f48483ab25 --- /dev/null +++ b/util/archiveutil/compression.go @@ -0,0 +1,100 @@ +package archiveutil + +import ( + "bufio" + "bytes" + "compress/bzip2" + "context" + "io" + "os/exec" + + cdcompression "github.com/containerd/containerd/v2/pkg/archive/compression" + "github.com/pkg/errors" +) + +var ( + // bzip2 streams start with ASCII "BZh". + // See https://www.loc.gov/preservation/digital/formats/fdd/fdd000600.shtml + bzip2Magic = []byte{0x42, 0x5a, 0x68} + + // XZ streams start with these six header magic bytes. + // See https://tukaani.org/xz/xz-file-format.txt + xzMagic = []byte{0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00} +) + +type readCloser struct { + io.Reader + close func() error +} + +func (r readCloser) Close() error { + if r.close != nil { + return r.close() + } + return nil +} + +// DecompressStream decompresses Docker-compatible archive streams. +func DecompressStream(r io.Reader) (io.ReadCloser, error) { + buf := bufio.NewReaderSize(r, 32*1024) + bs, err := buf.Peek(10) + if err != nil && err != io.EOF { + return nil, err + } + + switch { + case bytes.HasPrefix(bs, bzip2Magic): + return readCloser{Reader: bzip2.NewReader(buf)}, nil + case bytes.HasPrefix(bs, xzMagic): + ctx, cancel := context.WithCancelCause(context.Background()) + xzReader, err := cmdStream(exec.CommandContext(ctx, "xz", "-d", "-c", "-q"), buf) + if err != nil { + cancel(err) + return nil, err + } + return readCloser{ + Reader: xzReader, + close: func() error { + cancel(nil) + return xzReader.Close() + }, + }, nil + default: + return cdcompression.DecompressStream(buf) + } +} + +func cmdStream(cmd *exec.Cmd, in io.Reader) (io.ReadCloser, error) { + reader, writer := io.Pipe() + cmd.Stdin = in + cmd.Stdout = writer + + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + if err := cmd.Start(); err != nil { + return nil, err + } + + done := make(chan struct{}) + go func() { + defer close(done) + if err := cmd.Wait(); err != nil { + if errBuf.Len() > 0 { + err = errors.Wrapf(err, "%s", errBuf.String()) + } + writer.CloseWithError(err) + } else { + writer.Close() + } + }() + + return readCloser{ + Reader: reader, + close: func() error { + err := reader.Close() + <-done + return err + }, + }, nil +} diff --git a/util/archiveutil/compression_test.go b/util/archiveutil/compression_test.go new file mode 100644 index 000000000000..79c2d4b2238a --- /dev/null +++ b/util/archiveutil/compression_test.go @@ -0,0 +1,97 @@ +package archiveutil + +import ( + "bytes" + "compress/gzip" + "io" + "os/exec" + "testing" + + "github.com/klauspost/compress/zstd" + "github.com/stretchr/testify/require" +) + +func TestDecompressStream(t *testing.T) { + t.Parallel() + + gz := bytes.NewBuffer(nil) + gzw := gzip.NewWriter(gz) + _, err := gzw.Write([]byte("payload")) + require.NoError(t, err) + require.NoError(t, gzw.Close()) + + zstdBuf := bytes.NewBuffer(nil) + zstdw, err := zstd.NewWriter(zstdBuf) + require.NoError(t, err) + _, err = zstdw.Write([]byte("payload")) + require.NoError(t, err) + require.NoError(t, zstdw.Close()) + + tests := []struct { + name string + dt []byte + }{ + { + name: "uncompressed", + dt: []byte("payload"), + }, + { + name: "gzip", + dt: gz.Bytes(), + }, + { + name: "bzip2", + dt: []byte{ + 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, + 0x53, 0x59, 0x1d, 0x1b, 0x8a, 0x0d, 0x00, 0x00, + 0x02, 0x81, 0x80, 0x24, 0x04, 0xc0, 0x20, 0x20, + 0x00, 0x22, 0x18, 0x68, 0x30, 0x09, 0x58, 0x13, + 0x0b, 0xb9, 0x22, 0x9c, 0x28, 0x48, 0x0e, 0x8d, + 0xc5, 0x06, 0x80, + }, + }, + { + name: "zstd", + dt: zstdBuf.Bytes(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rc, err := DecompressStream(bytes.NewReader(tt.dt)) + require.NoError(t, err) + defer rc.Close() + + dt, err := io.ReadAll(rc) + require.NoError(t, err) + require.Equal(t, "payload", string(dt)) + }) + } +} + +func TestDecompressStreamXz(t *testing.T) { + t.Parallel() + + if _, err := exec.LookPath("xz"); err != nil { + t.Skip("xz binary is not available") + } + + rc, err := DecompressStream(bytes.NewReader([]byte{ + 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04, + 0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01, + 0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3, + 0x01, 0x00, 0x06, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x00, 0x00, 0x8b, 0x1f, 0xd0, 0x83, + 0x9e, 0x49, 0x8f, 0x37, 0x00, 0x01, 0x1f, 0x07, + 0x16, 0x2e, 0xb8, 0x73, 0x1f, 0xb6, 0xf3, 0x7d, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a, + })) + require.NoError(t, err) + defer rc.Close() + + dt, err := io.ReadAll(rc) + require.NoError(t, err) + require.Equal(t, "payload", string(dt)) +} diff --git a/vendor/github.com/moby/go-archive/.gitattributes b/vendor/github.com/moby/go-archive/.gitattributes deleted file mode 100644 index 4bb139d900bd..000000000000 --- a/vendor/github.com/moby/go-archive/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.go -text diff=golang -*.go text eol=lf diff --git a/vendor/github.com/moby/go-archive/.gitignore b/vendor/github.com/moby/go-archive/.gitignore deleted file mode 100644 index 3f2bc47416e7..000000000000 --- a/vendor/github.com/moby/go-archive/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/coverage.txt diff --git a/vendor/github.com/moby/go-archive/.golangci.yml b/vendor/github.com/moby/go-archive/.golangci.yml deleted file mode 100644 index 21439e5c6951..000000000000 --- a/vendor/github.com/moby/go-archive/.golangci.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: "2" - -issues: - # Disable maximum issues count per one linter. - max-issues-per-linter: 0 - # Disable maximum count of issues with the same text. - max-same-issues: 0 - -linters: - enable: - - errorlint - - unconvert - - unparam - exclusions: - generated: disable - presets: - - comments - - std-error-handling - settings: - staticcheck: - # Enable all options, with some exceptions. - # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck - checks: - - all - - -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008 - - -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003 - -formatters: - enable: - - gofumpt - - goimports - exclusions: - generated: disable diff --git a/vendor/github.com/moby/go-archive/LICENSE b/vendor/github.com/moby/go-archive/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/go-archive/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go deleted file mode 100644 index 8dce2e6e2afa..000000000000 --- a/vendor/github.com/moby/go-archive/archive.go +++ /dev/null @@ -1,1129 +0,0 @@ -// Package archive provides helper functions for dealing with archive files. -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - "syscall" - "time" - - "github.com/containerd/log" - "github.com/moby/patternmatcher" - "github.com/moby/sys/sequential" - "github.com/moby/sys/user" - - "github.com/moby/go-archive/compression" - "github.com/moby/go-archive/tarheader" -) - -// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a -// tar, but that do not have their own header entry. -// -// The permissions mask is stored in a constant instead of locally to ensure that magic numbers do not -// proliferate in the codebase. The default value 0755 has been selected based on the default umask of 0022, and -// a convention of mkdir(1) calling mkdir(2) with permissions of 0777, resulting in a final value of 0755. -// -// This value is currently implementation-defined, and not captured in any cross-runtime specification. Thus, it is -// subject to change in Moby at any time -- image authors who require consistent or known directory permissions -// should explicitly control them by ensuring that header entries exist for any applicable path. -const ImpliedDirectoryMode = 0o755 - -type ( - // WhiteoutFormat is the format of whiteouts unpacked - WhiteoutFormat int - - ChownOpts struct { - UID int - GID int - } - - // TarOptions wraps the tar options. - TarOptions struct { - IncludeFiles []string - ExcludePatterns []string - Compression compression.Compression - NoLchown bool - IDMap user.IdentityMapping - ChownOpts *ChownOpts - IncludeSourceDir bool - // WhiteoutFormat is the expected on disk format for whiteout files. - // This format will be converted to the standard format on pack - // and from the standard format on unpack. - WhiteoutFormat WhiteoutFormat - // When unpacking, specifies whether overwriting a directory with a - // non-directory is allowed and vice versa. - NoOverwriteDirNonDir bool - // For each include when creating an archive, the included name will be - // replaced with the matching name from this map. - RebaseNames map[string]string - InUserNS bool - // Allow unpacking to succeed in spite of failures to set extended - // attributes on the unpacked files due to the destination filesystem - // not supporting them or a lack of permissions. Extended attributes - // were probably in the archive for a reason, so set this option at - // your own peril. - BestEffortXattrs bool - } -) - -// Archiver implements the Archiver interface and allows the reuse of most utility functions of -// this package with a pluggable Untar function. Also, to facilitate the passing of specific id -// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. -type Archiver struct { - Untar func(io.Reader, string, *TarOptions) error - IDMapping user.IdentityMapping -} - -// NewDefaultArchiver returns a new Archiver without any IdentityMapping -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 - -const ( - AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts - OverlayWhiteoutFormat WhiteoutFormat = 1 // OverlayWhiteoutFormat formats whiteout according to the overlay standard. -) - -// 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) - if err != nil { - return false - } - defer file.Close() - rdr, err := compression.DecompressStream(file) - if err != nil { - return false - } - defer rdr.Close() - r := tar.NewReader(rdr) - _, err = r.Next() - return err == nil -} - -// TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to -// modify the contents or header of an entry in the archive. If the file already -// exists in the archive the TarModifierFunc will be called with the Header and -// a reader which will return the files content. If the file does not exist both -// header and content will be nil. -type TarModifierFunc func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) - -// ReplaceFileTarWrapper converts inputTarStream to a new tar stream. Files in the -// tar stream are modified if they match any of the keys in mods. -func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModifierFunc) io.ReadCloser { - pipeReader, pipeWriter := io.Pipe() - - go func() { - tarReader := tar.NewReader(inputTarStream) - tarWriter := tar.NewWriter(pipeWriter) - defer inputTarStream.Close() - defer tarWriter.Close() - - modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { - header, data, err := modifier(name, original, tarReader) - switch { - case err != nil: - return err - case header == nil: - return nil - } - - if header.Name == "" { - header.Name = name - } - header.Size = int64(len(data)) - if err := tarWriter.WriteHeader(header); err != nil { - return err - } - if len(data) != 0 { - if _, err := tarWriter.Write(data); err != nil { - return err - } - } - return nil - } - - var err error - var originalHeader *tar.Header - for { - originalHeader, err = tarReader.Next() - if errors.Is(err, io.EOF) { - break - } - if err != nil { - pipeWriter.CloseWithError(err) - return - } - - modifier, ok := mods[originalHeader.Name] - if !ok { - // No modifiers for this file, copy the header and data - if err := tarWriter.WriteHeader(originalHeader); err != nil { - pipeWriter.CloseWithError(err) - return - } - if err := copyWithBuffer(tarWriter, tarReader); err != nil { - pipeWriter.CloseWithError(err) - return - } - continue - } - delete(mods, originalHeader.Name) - - if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { - pipeWriter.CloseWithError(err) - return - } - } - - // 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) - return - } - } - - pipeWriter.Close() - }() - return pipeReader -} - -// FileInfoHeader creates a populated Header from fi. -// -// Compared to the archive/tar package, this function fills in less information -// but is safe to call from a chrooted process. The AccessTime and ChangeTime -// fields are not set in the returned header, ModTime is truncated to one-second -// precision, and the Uname and Gname fields are only set when fi is a FileInfo -// value returned from tar.Header.FileInfo(). -func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link) - if err != nil { - return nil, err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - hdr.Name = canonicalTarName(name, fi.IsDir()) - return hdr, nil -} - -const paxSchilyXattr = "SCHILY.xattr." - -// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem -// to a tar header -func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { - const ( - // Values based on linux/include/uapi/linux/capability.h - xattrCapsSz2 = 20 - versionOffset = 3 - vfsCapRevision2 = 2 - vfsCapRevision3 = 3 - ) - capability, _ := lgetxattr(path, "security.capability") - if capability != nil { - if capability[versionOffset] == vfsCapRevision3 { - // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no - // sense outside the user namespace the archive is built in. - capability[versionOffset] = vfsCapRevision2 - capability = capability[:xattrCapsSz2] - } - if hdr.PAXRecords == nil { - hdr.PAXRecords = make(map[string]string) - } - hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability) - } - return nil -} - -type tarWhiteoutConverter interface { - ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) - ConvertRead(*tar.Header, string) (bool, error) -} - -type tarAppender struct { - TarWriter *tar.Writer - - // for hardlink mapping - SeenFiles map[uint64]string - IdentityMapping user.IdentityMapping - ChownOpts *ChownOpts - - // For packing and unpacking whiteout files in the - // non standard format. The whiteout files defined - // by the AUFS standard are used as the tar whiteout - // standard. - WhiteoutConverter tarWhiteoutConverter -} - -func newTarAppender(idMapping user.IdentityMapping, writer io.Writer, chownOpts *ChownOpts) *tarAppender { - return &tarAppender{ - SeenFiles: make(map[uint64]string), - TarWriter: tar.NewWriter(writer), - IdentityMapping: idMapping, - ChownOpts: chownOpts, - } -} - -// canonicalTarName provides a platform-independent and consistent POSIX-style -// path for files and directories to be archived regardless of the platform. -func canonicalTarName(name string, isDir bool) string { - name = filepath.ToSlash(name) - - // suffix with '/' for directories - if isDir && !strings.HasSuffix(name, "/") { - name += "/" - } - 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) - if err != nil { - return err - } - - var link string - if fi.Mode()&os.ModeSymlink != 0 { - var err error - link, err = os.Readlink(path) - if err != nil { - return err - } - } - - hdr, err := FileInfoHeader(name, fi, link) - if err != nil { - return err - } - if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { - return err - } - - // if it's not a directory and has more than 1 link, - // it's hard linked, so set the type flag accordingly - if !fi.IsDir() && hasHardlinks(fi) { - inode, err := getInodeFromStat(fi.Sys()) - if err != nil { - return err - } - // a link should have a name that it links too - // and that linked name should be first in the tar archive - if oldpath, ok := ta.SeenFiles[inode]; ok { - hdr.Typeflag = tar.TypeLink - hdr.Linkname = oldpath - hdr.Size = 0 // This Must be here for the writer math to add up! - } else { - ta.SeenFiles[inode] = name - } - } - - // check whether the file is overlayfs whiteout - // if yes, skip re-mapping container ID mappings. - isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 - - // handle re-mapping container ID mappings back to host ID mappings before - // writing tar headers/files. We skip whiteout files because they were written - // by the kernel and already have proper ownership relative to the host - if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { - uid, gid, err := getFileUIDGID(fi.Sys()) - if err != nil { - return err - } - hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(uid, gid) - if err != nil { - return err - } - } - - // explicitly override with ChownOpts - if ta.ChownOpts != nil { - hdr.Uid = ta.ChownOpts.UID - hdr.Gid = ta.ChownOpts.GID - } - - if ta.WhiteoutConverter != nil { - wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) - if err != nil { - return err - } - - // If a new whiteout file exists, write original hdr, then - // replace hdr with wo to be written after. Whiteouts should - // always be written after the original. Note the original - // hdr may have been updated to be a whiteout with returning - // a whiteout header - if wo != nil { - 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 - } - } - - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - return err - } - - 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) - if err != nil { - return err - } - - err = copyWithBuffer(ta.TarWriter, file) - file.Close() - if err != nil { - return err - } - } - - return nil -} - -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { - var ( - Lchown = true - inUserns, bestEffortXattrs bool - chownOpts *ChownOpts - ) - - // TODO(thaJeztah): make opts a required argument. - if opts != nil { - Lchown = !opts.NoLchown - inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally. - chownOpts = opts.ChownOpts - bestEffortXattrs = opts.BestEffortXattrs - } - - // hdr.Mode is in linux format, which we can use for sycalls, - // but for os.Foo() calls we need the mode converted to os.FileMode, - // so use hdrInfo.Mode() (they differ for e.g. setuid bits) - hdrInfo := hdr.FileInfo() - - switch hdr.Typeflag { - case tar.TypeDir: - // Create directory unless it exists as a directory already. - // In that case we just want to merge the two - if fi, err := os.Lstat(path); err != nil || !fi.IsDir() { - if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { - return err - } - } - - case tar.TypeReg: - // Source is regular file. We use sequential file access to avoid depleting - // the standby list on Windows. On Linux, this equates to a regular os.OpenFile. - file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) - if err != nil { - return err - } - if err := copyWithBuffer(file, reader); err != nil { - _ = file.Close() - return err - } - _ = file.Close() - - 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") - return nil - } - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { - return err - } - - case tar.TypeFifo: - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { - if inUserns && errors.Is(err, syscall.EPERM) { - // In most cases, cannot create a fifo if running in user namespace - log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") - return nil - } - return err - } - - case tar.TypeLink: - // #nosec G305 -- The target path is checked for path traversal. - targetPath := 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 err := os.Link(targetPath, path); err != nil { - return err - } - - case tar.TypeSymlink: - // path -> hdr.Linkname = targetPath - // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file - targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. - - // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because - // that symlink would first have to be created, which would be caught earlier, at this very check: - if !strings.HasPrefix(targetPath, extractDir) { - return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) - } - if err := os.Symlink(hdr.Linkname, path); err != nil { - return err - } - - case tar.TypeXGlobalHeader: - log.G(context.TODO()).Debug("PAX Global Extended Headers found and ignored") - return nil - - default: - return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) - } - - // Lchown is not supported on Windows. - if Lchown && runtime.GOOS != "windows" { - if chownOpts == nil { - chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid} - } - if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { - var msg string - if inUserns && errors.Is(err, syscall.EINVAL) { - msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" - } - return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err) - } - } - - var xattrErrs []string - for key, value := range hdr.PAXRecords { - xattr, ok := strings.CutPrefix(key, paxSchilyXattr) - if !ok { - continue - } - if err := lsetxattr(path, xattr, []byte(value), 0); err != nil { - if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) { - // EPERM occurs if modifying xattrs is not allowed. This can - // happen when running in userns with restrictions (ChromeOS). - xattrErrs = append(xattrErrs, err.Error()) - continue - } - return err - } - } - - if len(xattrErrs) > 0 { - log.G(context.TODO()).WithFields(log.Fields{ - "errors": xattrErrs, - }).Warn("ignored xattrs in archive: underlying filesystem doesn't support them") - } - - // 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 { - 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 { - 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 { - return err - } - } - return nil -} - -// Tar creates an archive from the directory at `path`, and returns it as a -// stream of bytes. -func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { - return TarWithOptions(path, &TarOptions{Compression: comp}) -} - -// TarWithOptions creates an archive from the directory at `path`, only including files whose relative -// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. -func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { - tb, err := NewTarballer(srcPath, options) - if err != nil { - return nil, err - } - go tb.Do() - return tb.Reader(), nil -} - -// Tarballer is a lower-level interface to TarWithOptions which gives the caller -// control over which goroutine the archiving operation executes on. -type Tarballer struct { - srcPath string - options *TarOptions - pm *patternmatcher.PatternMatcher - pipeReader *io.PipeReader - pipeWriter *io.PipeWriter - compressWriter io.WriteCloser - whiteoutConverter tarWhiteoutConverter -} - -// NewTarballer constructs a new tarballer. The arguments are the same as for -// TarWithOptions. -func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { - pm, err := patternmatcher.New(options.ExcludePatterns) - if err != nil { - return nil, err - } - - pipeReader, pipeWriter := io.Pipe() - - compressWriter, err := compression.CompressStream(pipeWriter, options.Compression) - if err != nil { - return nil, err - } - - return &Tarballer{ - // Fix the source path to work with long path names. This is a no-op - // on platforms other than Windows. - srcPath: addLongPathPrefix(srcPath), - options: options, - pm: pm, - pipeReader: pipeReader, - pipeWriter: pipeWriter, - compressWriter: compressWriter, - whiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat), - }, nil -} - -// Reader returns the reader for the created archive. -func (t *Tarballer) Reader() io.ReadCloser { - return t.pipeReader -} - -// Do performs the archiving operation in the background. The resulting archive -// can be read from t.Reader(). Do should only be called once on each Tarballer -// instance. -func (t *Tarballer) Do() { - ta := newTarAppender( - t.options.IDMap, - t.compressWriter, - t.options.ChownOpts, - ) - ta.WhiteoutConverter = t.whiteoutConverter - - defer func() { - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close tar writer: %s", err) - } - if err := t.compressWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close compress writer: %s", err) - } - if err := t.pipeWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close pipe writer: %s", err) - } - }() - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - - stat, err := os.Lstat(t.srcPath) - if err != nil { - return - } - - if !stat.IsDir() { - // We can't later join a non-dir with any includes because the - // 'walk' will error if "file/." is stat-ed and "file" is not a - // directory. So, we must split the source path and use the - // basename as the include. - if len(t.options.IncludeFiles) > 0 { - log.G(context.TODO()).Warn("Tar: Can't archive a file with includes") - } - - dir, base := SplitPathDirEntry(t.srcPath) - t.srcPath = dir - t.options.IncludeFiles = []string{base} - } - - if len(t.options.IncludeFiles) == 0 { - t.options.IncludeFiles = []string{"."} - } - - seen := make(map[string]bool) - - for _, include := range t.options.IncludeFiles { - rebaseName := t.options.RebaseNames[include] - - var ( - parentMatchInfo []patternmatcher.MatchInfo - parentDirs []string - ) - - walkRoot := getWalkRoot(t.srcPath, include) - // TODO(thaJeztah): should this error be handled? - _ = filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { - if err != nil { - log.G(context.TODO()).Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err) - return nil - } - - relFilePath, err := filepath.Rel(t.srcPath, filePath) - if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { - // Error getting relative path OR we are looking - // at the source directory path. Skip in both situations. - return nil - } - - if t.options.IncludeSourceDir && include == "." && relFilePath != "." { - relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) - } - - skip := false - - // If "include" is an exact match for the current file - // then even if there's an "excludePatterns" pattern that - // matches it, don't skip it. IOW, assume an explicit 'include' - // is asking for that file no matter what - which is true - // for some files, like .dockerignore and Dockerfile (sometimes) - if include != relFilePath { - for len(parentDirs) != 0 { - lastParentDir := parentDirs[len(parentDirs)-1] - if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) { - break - } - parentDirs = parentDirs[:len(parentDirs)-1] - parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1] - } - - var matchInfo patternmatcher.MatchInfo - if len(parentMatchInfo) != 0 { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1]) - } else { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{}) - } - if err != nil { - log.G(context.TODO()).Errorf("Error matching %s: %v", relFilePath, err) - return err - } - - if f.IsDir() { - parentDirs = append(parentDirs, relFilePath) - parentMatchInfo = append(parentMatchInfo, matchInfo) - } - } - - if skip { - // If we want to skip this file and its a directory - // then we should first check to see if there's an - // excludes pattern (e.g. !dir/file) that starts with this - // dir. If so then we can't skip this dir. - - // Its not a dir then so we can just return/skip. - if !f.IsDir() { - return nil - } - - // No exceptions (!...) in patterns so just skip dir - if !t.pm.Exclusions() { - return filepath.SkipDir - } - - dirSlash := relFilePath + string(filepath.Separator) - - for _, pat := range t.pm.Patterns() { - if !pat.Exclusion() { - continue - } - if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { - // found a match - so can't skip this dir - return nil - } - } - - // No matching exclusion dir so just skip dir - return filepath.SkipDir - } - - if seen[relFilePath] { - return nil - } - seen[relFilePath] = true - - // Rename the base resource. - if rebaseName != "" { - var replacement string - if rebaseName != string(filepath.Separator) { - // Special case the root directory to replace with an - // empty string instead so that we don't end up with - // double slashes in the paths. - replacement = rebaseName - } - - relFilePath = strings.Replace(relFilePath, include, replacement, 1) - } - - if err := ta.addTarFile(filePath, relFilePath); err != nil { - log.G(context.TODO()).Errorf("Can't add file %s to tar: %s", filePath, err) - // if pipe is broken, stop writing tar stream to it - if errors.Is(err, io.ErrClosedPipe) { - return err - } - } - return nil - }) - } -} - -// Unpack unpacks the decompressedArchive to dest with options. -func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { - tr := tar.NewReader(decompressedArchive) - - var dirs []*tar.Header - whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) - - // Iterate through the files in the archive. -loop: - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return err - } - - // ignore XGlobalHeader early to avoid creating parent directories for them - if hdr.Typeflag == tar.TypeXGlobalHeader { - log.G(context.TODO()).Debugf("PAX Global Extended Headers found for %s and ignored", hdr.Name) - continue - } - - // Normalize name, for safety and for a simple is-root check - // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: - // This keeps "..\" as-is, but normalizes "\..\" to "\". - hdr.Name = filepath.Clean(hdr.Name) - - for _, exclude := range options.ExcludePatterns { - if strings.HasPrefix(hdr.Name, exclude) { - continue loop - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return err - } - - // #nosec G305 -- The joined path is checked for path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) - if err != nil { - return err - } - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - - // If path exits we almost always just want to remove and replace it - // 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 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) - } - - 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) - } - - if fi.IsDir() && hdr.Name == "." { - continue - } - - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { - return err - } - } - } - - if err := remapIDs(options.IDMap, hdr); err != nil { - return err - } - - if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, path) - if err != nil { - return err - } - if !writeFile { - continue - } - } - - if err := createTarFile(path, dest, hdr, tr, options); err != nil { - return err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - } - - 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 { - return err - } - } - return nil -} - -// createImpliedDirectories will create all parent directories of the current path with default permissions, if they do -// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is -// defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus -// we most both create them and choose metadata like permissions. -// -// 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)) { - parent := filepath.Dir(hdr.Name) - parentPath := filepath.Join(dest, parent) - if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some - // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche - // usage that reduces the portability of an image. - uid, gid := options.IDMap.RootPair() - - err = user.MkdirAllAndChown(parentPath, ImpliedDirectoryMode, uid, gid, user.WithOnlyNew) - if err != nil { - return err - } - } - } - - return nil -} - -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive may be compressed with one of the following algorithms: -// identity (uncompressed), gzip, bzip2, xz. -// -// FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, true) -} - -// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive must be an uncompressed stream. -func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, false) -} - -// Handler for teasing out the automatic decompression -func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { - if tarArchive == nil { - return errors.New("empty archive") - } - dest = filepath.Clean(dest) - if options == nil { - options = &TarOptions{} - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - - r := tarArchive - if decompress { - decompressedArchive, err := compression.DecompressStream(tarArchive) - if err != nil { - return err - } - defer decompressedArchive.Close() - r = decompressedArchive - } - - return Unpack(r, dest, options) -} - -// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. -// If either Tar or Untar fails, TarUntar aborts and returns the error. -func (archiver *Archiver) TarUntar(src, dst string) error { - archive, err := Tar(src, compression.None) - if err != nil { - return err - } - defer archive.Close() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// UntarPath untar a file from path to a destination, src is the source tar file path. -func (archiver *Archiver) UntarPath(src, dst string) error { - archive, err := os.Open(src) - if err != nil { - return err - } - defer archive.Close() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// CopyWithTar creates a tar archive of filesystem path `src`, and -// unpacks it at filesystem path `dst`. -// The archive is streamed directly with fixed buffering and no -// intermediary disk IO. -func (archiver *Archiver) CopyWithTar(src, dst string) error { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - if !srcSt.IsDir() { - return archiver.CopyFileWithTar(src, dst) - } - - // if this Archiver is set up with ID mapping we need to create - // the new destination directory with the remapped root UID/GID pair - // as owner - uid, gid := archiver.IDMapping.RootPair() - // Create dst, copy src's content into it - if err := user.MkdirAllAndChown(dst, 0o755, uid, gid, user.WithOnlyNew); err != nil { - return err - } - return archiver.TarUntar(src, dst) -} - -// CopyFileWithTar emulates the behavior of the 'cp' command-line -// for a single file. It copies a regular file from path `src` to -// path `dst`, and preserves all its metadata. -func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - - if srcSt.IsDir() { - return errors.New("can't copy a directory") - } - - // Clean up the trailing slash. This must be done in an operating - // system specific manner. - if dst[len(dst)-1] == os.PathSeparator { - dst = filepath.Join(dst, filepath.Base(src)) - } - // Create the holding directory if necessary - if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil { - return err - } - - r, w := io.Pipe() - errC := make(chan error, 1) - - go func() { - defer close(errC) - - errC <- func() error { - defer w.Close() - - srcF, err := os.Open(src) - if err != nil { - return err - } - defer srcF.Close() - - hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") - if err != nil { - return err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Name = filepath.Base(dst) - hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - - if err := remapIDs(archiver.IDMapping, hdr); err != nil { - return err - } - - tw := tar.NewWriter(w) - defer tw.Close() - if err := tw.WriteHeader(hdr); err != nil { - return err - } - if err := copyWithBuffer(tw, srcF); err != nil { - return err - } - return nil - }() - }() - defer func() { - if er := <-errC; err == nil && er != nil { - err = er - } - }() - - err = archiver.Untar(r, filepath.Dir(dst), nil) - if err != nil { - r.CloseWithError(err) - } - return err -} - -// IdentityMapping returns the IdentityMapping of the archiver. -func (archiver *Archiver) IdentityMapping() user.IdentityMapping { - return archiver.IDMapping -} - -func remapIDs(idMapping user.IdentityMapping, hdr *tar.Header) error { - uid, gid, err := idMapping.ToHost(hdr.Uid, hdr.Gid) - hdr.Uid, hdr.Gid = uid, gid - return err -} diff --git a/vendor/github.com/moby/go-archive/archive_linux.go b/vendor/github.com/moby/go-archive/archive_linux.go deleted file mode 100644 index 7b6c3e02b643..000000000000 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ /dev/null @@ -1,107 +0,0 @@ -package archive - -import ( - "archive/tar" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/moby/sys/userns" - "golang.org/x/sys/unix" -) - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - if format == OverlayWhiteoutFormat { - return overlayWhiteoutConverter{} - } - return nil -} - -type overlayWhiteoutConverter struct{} - -func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) { - // convert whiteouts to AUFS format - if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { - // we just rename the file and make it normal - dir, filename := filepath.Split(hdr.Name) - hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) - hdr.Mode = 0o600 - hdr.Typeflag = tar.TypeReg - hdr.Size = 0 - } - - if fi.Mode()&os.ModeDir == 0 { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - - opaqueXattrName := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattrName = "user.overlay.opaque" - } - - // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(path, opaqueXattrName) - if err != nil { - return nil, err - } - if len(opaque) != 1 || opaque[0] != 'y' { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) - - // create a header for the whiteout file - // it should inherit some properties from the parent, but be a regular file - return &tar.Header{ - Typeflag: tar.TypeReg, - Mode: hdr.Mode & int64(os.ModePerm), - Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. - Size: 0, - Uid: hdr.Uid, - Uname: hdr.Uname, - Gid: hdr.Gid, - Gname: hdr.Gname, - AccessTime: hdr.AccessTime, - ChangeTime: hdr.ChangeTime, - }, nil -} - -func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { - base := filepath.Base(path) - dir := filepath.Dir(path) - - // 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" - } - - err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0) - if err != nil { - return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err) - } - // don't write the file itself - return false, err - } - - // if a file was deleted and we are using overlay, we need to create a character device - if strings.HasPrefix(base, WhiteoutPrefix) { - originalBase := base[len(WhiteoutPrefix):] - originalPath := filepath.Join(dir, originalBase) - - if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { - return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err) - } - if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { - return false, err - } - - // don't write the file itself - return false, nil - } - - return true, nil -} diff --git a/vendor/github.com/moby/go-archive/archive_other.go b/vendor/github.com/moby/go-archive/archive_other.go deleted file mode 100644 index 6495549f60e8..000000000000 --- a/vendor/github.com/moby/go-archive/archive_other.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package archive - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go deleted file mode 100644 index 3a9f5b0b5863..000000000000 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build !windows - -package archive - -import ( - "archive/tar" - "errors" - "os" - "path/filepath" - "strings" - "syscall" - - "golang.org/x/sys/unix" -) - -// 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 { - return srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. On Linux, we -// can't use filepath.Join(srcPath,include) because this will clean away -// a trailing "." or "/" which may be important. -func getWalkRoot(srcPath string, include string) string { - return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include -} - -// 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 getInodeFromStat(stat interface{}) (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 s.Ino, nil -} - -func getFileUIDGID(stat interface{}) (int, int, error) { - s, ok := stat.(*syscall.Stat_t) - - if !ok { - return 0, 0, errors.New("cannot convert stat value to syscall.Stat_t") - } - return int(s.Uid), int(s.Gid), nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo. -// -// 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 { - mode := uint32(hdr.Mode & 0o7777) - switch hdr.Typeflag { - case tar.TypeBlock: - mode |= unix.S_IFBLK - case tar.TypeChar: - mode |= unix.S_IFCHR - case tar.TypeFifo: - mode |= unix.S_IFIFO - } - - return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) -} - -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go deleted file mode 100644 index 0e3e316afc2f..000000000000 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ /dev/null @@ -1,62 +0,0 @@ -package archive - -import ( - "archive/tar" - "os" - "path/filepath" - "strings" -) - -// longPathPrefix is the longpath prefix for Windows file paths. -const longPathPrefix = `\\?\` - -// 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. -// -// addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. -func addLongPathPrefix(srcPath string) string { - if strings.HasPrefix(srcPath, longPathPrefix) { - return srcPath - } - if strings.HasPrefix(srcPath, `\\`) { - // This is a UNC path, so we need to add 'UNC' to the path as well. - return longPathPrefix + `UNC` + srcPath[1:] - } - return longPathPrefix + srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. -func getWalkRoot(srcPath string, include string) string { - return filepath.Join(srcPath, include) -} - -// 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 { - // Remove group- and world-writable bits. - perm &= 0o755 - - // Add the x bit: make everything +x on Windows - return perm | 0o111 -} - -func getInodeFromStat(stat interface{}) (uint64, error) { - // do nothing. no notion of Inode in stat on Windows - return 0, nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo -func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { - return nil -} - -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - return nil -} - -func getFileUIDGID(stat interface{}) (int, int, error) { - // no notion of file ownership mapping yet on Windows - return 0, 0, nil -} diff --git a/vendor/github.com/moby/go-archive/changes.go b/vendor/github.com/moby/go-archive/changes.go deleted file mode 100644 index 02a0372c6f99..000000000000 --- a/vendor/github.com/moby/go-archive/changes.go +++ /dev/null @@ -1,430 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "context" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/containerd/log" - "github.com/moby/sys/user" -) - -// ChangeType represents the change type. -type ChangeType int - -const ( - ChangeModify = 0 // ChangeModify represents the modify operation. - ChangeAdd = 1 // ChangeAdd represents the add operation. - ChangeDelete = 2 // ChangeDelete represents the delete operation. -) - -func (c ChangeType) String() string { - switch c { - case ChangeModify: - return "C" - case ChangeAdd: - return "A" - case ChangeDelete: - return "D" - } - return "" -} - -// Change represents a change, it wraps the change type and path. -// It describes changes of the files in the path respect to the -// parent layers. The change could be modify, add, delete. -// This is used for layer diff. -type Change struct { - Path string - Kind ChangeType -} - -func (change *Change) String() string { - return fmt.Sprintf("%s %s", change.Kind, change.Path) -} - -// for sort.Sort -type changesByPath []Change - -func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path } -func (c changesByPath) Len() int { return len(c) } -func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] } - -// Gnu tar doesn't have sub-second mtime precision. The go tar -// writer (1.10+) does when using PAX format, but we round times to seconds -// to ensure archives have the same hashes for backwards compatibility. -// See https://github.com/moby/moby/pull/35739/commits/fb170206ba12752214630b269a40ac7be6115ed4. -// -// Non-sub-second is problematic when we apply changes via tar -// files. We handle this by comparing for exact times, *or* same -// second count and either a or b having exactly 0 nanoseconds -func sameFsTime(a, b time.Time) bool { - return a.Equal(b) || - (a.Unix() == b.Unix() && - (a.Nanosecond() == 0 || b.Nanosecond() == 0)) -} - -// Changes walks the path rw and determines changes for the files in the path, -// with respect to the parent layers -func Changes(layers []string, rw string) ([]Change, error) { - return collectChanges(layers, rw, aufsDeletedFile, aufsMetadataSkip) -} - -func aufsMetadataSkip(path string) (skip bool, err error) { - skip, err = filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path) - if err != nil { - skip = true - } - return skip, err -} - -func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) { - f := filepath.Base(path) - - // If there is a whiteout, then the file was removed - if strings.HasPrefix(f, WhiteoutPrefix) { - originalFile := f[len(WhiteoutPrefix):] - return filepath.Join(filepath.Dir(path), originalFile), nil - } - - return "", nil -} - -type ( - skipChange func(string) (bool, error) - deleteChange func(string, string, os.FileInfo) (string, error) -) - -func collectChanges(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { - var ( - changes []Change - changedDirs = make(map[string]struct{}) - ) - - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - path = filepath.Join(string(os.PathSeparator), path) - - // Skip root - if path == string(os.PathSeparator) { - return nil - } - - if sc != nil { - if skip, err := sc(path); skip { - return err - } - } - - change := Change{ - Path: path, - } - - deletedFile, err := dc(rw, path, f) - if err != nil { - return err - } - - // Find out what kind of modification happened - if deletedFile != "" { - change.Path = deletedFile - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files. - // This block is here to ensure the change is recorded even if the - // modify time, mode and size of the parent directory in the rw and ro layers are all equal. - // Check https://github.com/docker/docker/pull/13590 for details. - if f.IsDir() { - changedDirs[path] = struct{}{} - } - if change.Kind == ChangeAdd || change.Kind == ChangeDelete { - parent := filepath.Dir(path) - if _, ok := changedDirs[parent]; !ok && parent != "/" { - changes = append(changes, Change{Path: parent, Kind: ChangeModify}) - changedDirs[parent] = struct{}{} - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - -// FileInfo describes the information of a file. -type FileInfo struct { - parent *FileInfo - name string - stat fs.FileInfo - children map[string]*FileInfo - capability []byte - added bool -} - -// LookUp looks up the file information of a file. -func (info *FileInfo) LookUp(path string) *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - parent := info - if path == string(os.PathSeparator) { - return info - } - - pathElements := strings.Split(path, string(os.PathSeparator)) - for _, elem := range pathElements { - if elem != "" { - child := parent.children[elem] - if child == nil { - return nil - } - parent = child - } - } - return parent -} - -func (info *FileInfo) path() string { - if info.parent == nil { - // As this runs on the daemon side, file paths are OS specific. - return string(os.PathSeparator) - } - return filepath.Join(info.parent.path(), info.name) -} - -func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { - sizeAtEntry := len(*changes) - - if oldInfo == nil { - // add - change := Change{ - Path: info.path(), - Kind: ChangeAdd, - } - *changes = append(*changes, change) - info.added = true - } - - // We make a copy so we can modify it to detect additions - // also, we only recurse on the old dir if the new info is a directory - // 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 - } - } - - for name, newChild := range info.children { - oldChild := oldChildren[name] - if oldChild != nil { - // change? - oldStat := oldChild.stat - newStat := newChild.stat - // Note: We can't compare inode or ctime or blocksize here, because these change - // when copying a file into a container. However, that is not generally a problem - // because any content change will change mtime, and any status change should - // be visible when actually comparing the stat fields. The only time this - // breaks down is if some code intentionally hides a change by setting - // back mtime - if statDifferent(oldStat, newStat) || - !bytes.Equal(oldChild.capability, newChild.capability) { - change := Change{ - Path: newChild.path(), - Kind: ChangeModify, - } - *changes = append(*changes, change) - newChild.added = true - } - - // Remove from copy so we can detect deletions - delete(oldChildren, name) - } - - newChild.addChanges(oldChild, changes) - } - for _, oldChild := range oldChildren { - // delete - change := Change{ - Path: oldChild.path(), - Kind: ChangeDelete, - } - *changes = append(*changes, change) - } - - // If there were changes inside this directory, we need to add it, even if the directory - // itself wasn't changed. This is needed to properly save and restore filesystem permissions. - // As this runs on the daemon side, file paths are OS specific. - if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) { - change := Change{ - Path: info.path(), - Kind: ChangeModify, - } - // Let's insert the directory entry before the recently added entries located inside this dir - *changes = append(*changes, change) // just to resize the slice, will be overwritten - copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:]) - (*changes)[sizeAtEntry] = change - } -} - -// Changes add changes to file information. -func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { - var changes []Change - - info.addChanges(oldInfo, &changes) - - return changes -} - -func newRootFileInfo() *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - root := &FileInfo{ - name: string(os.PathSeparator), - children: make(map[string]*FileInfo), - } - return root -} - -// ChangesDirs compares two directories and generates an array of Change objects describing the changes. -// If oldDir is "", then all files in newDir will be Add-Changes. -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var oldRoot, newRoot *FileInfo - if oldDir == "" { - emptyDir, err := os.MkdirTemp("", "empty") - if err != nil { - return nil, err - } - defer os.Remove(emptyDir) - oldDir = emptyDir - } - oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir) - if err != nil { - return nil, err - } - - return newRoot.Changes(oldRoot), nil -} - -// ChangesSize calculates the size in bytes of the provided changes, based on newDir. -func ChangesSize(newDir string, changes []Change) int64 { - var ( - size int64 - sf = make(map[uint64]struct{}) - ) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - file := filepath.Join(newDir, change.Path) - fileInfo, err := os.Lstat(file) - if err != nil { - log.G(context.TODO()).Errorf("Can not stat %q: %s", file, err) - continue - } - - if fileInfo != nil && !fileInfo.IsDir() { - if hasHardlinks(fileInfo) { - inode := getIno(fileInfo) - if _, ok := sf[inode]; !ok { - size += fileInfo.Size() - sf[inode] = struct{}{} - } - } else { - size += fileInfo.Size() - } - } - } - } - return size -} - -// ExportChanges produces an Archive from the provided changes, relative to dir. -func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io.ReadCloser, error) { - reader, writer := io.Pipe() - go func() { - ta := newTarAppender(idMap, writer, nil) - - sort.Sort(changesByPath(changes)) - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - for _, change := range changes { - if change.Kind == ChangeDelete { - whiteOutDir := filepath.Dir(change.Path) - whiteOutBase := filepath.Base(change.Path) - whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) - timestamp := time.Now() - hdr := &tar.Header{ - Name: whiteOut[1:], - Size: 0, - ModTime: timestamp, - AccessTime: timestamp, - ChangeTime: timestamp, - } - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - 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) - } - } - } - - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Debugf("Can't close layer: %s", err) - } - if err := writer.Close(); err != nil { - log.G(context.TODO()).Debugf("failed close Changes writer: %s", err) - } - }() - return reader, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go deleted file mode 100644 index 8289fe17d906..000000000000 --- a/vendor/github.com/moby/go-archive/changes_linux.go +++ /dev/null @@ -1,274 +0,0 @@ -package archive - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -// walker is used to implement collectFileInfoForChanges on linux. Where this -// method in general returns the entire contents of two directory trees, we -// optimize some FS calls out on linux. In particular, we take advantage of the -// fact that getdents(2) returns the inode of each file in the directory being -// walked, which, when walking two trees in parallel to generate a list of -// changes, can be used to prune subtrees without ever having to lstat(2) them -// directly. Eliminating stat calls in this way can save up to seconds on large -// images. -type walker struct { - dir1 string - dir2 string - root1 *FileInfo - root2 *FileInfo -} - -// collectFileInfoForChanges returns a complete representation of the trees -// rooted at dir1 and dir2, with one important exception: any subtree or -// leaf where the inode and device numbers are an exact match between dir1 -// and dir2 will be pruned from the results. This method is *only* to be used -// to generating a list of changes between the two directories, as it does not -// reflect the full contents. -func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) { - w := &walker{ - dir1: dir1, - dir2: dir2, - root1: newRootFileInfo(), - root2: newRootFileInfo(), - } - - i1, err := os.Lstat(w.dir1) - if err != nil { - return nil, nil, err - } - i2, err := os.Lstat(w.dir2) - if err != nil { - return nil, nil, err - } - - if err := w.walk("/", i1, i2); err != nil { - return nil, nil, err - } - - return w.root1, w.root2, nil -} - -// Given a FileInfo, its path info, and a reference to the root of the tree -// being constructed, register this file with the tree. -func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { - if fi == nil { - return nil - } - parent := root.LookUp(filepath.Dir(path)) - if parent == nil { - return fmt.Errorf("walkchunk: Unexpectedly no parent for %s", path) - } - info := &FileInfo{ - name: filepath.Base(path), - children: make(map[string]*FileInfo), - parent: parent, - } - cpath := filepath.Join(dir, path) - info.stat = fi - info.capability, _ = lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access - parent.children[info.name] = info - return nil -} - -// Walk a subtree rooted at the same path in both trees being iterated. For -// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d -func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) { - // Register these nodes with the return trees, unless we're still at the - // (already-created) roots: - if path != "/" { - if err := walkchunk(path, i1, w.dir1, w.root1); err != nil { - return err - } - if err := walkchunk(path, i2, w.dir2, w.root2); err != nil { - return err - } - } - - is1Dir := i1 != nil && i1.IsDir() - is2Dir := i2 != nil && i2.IsDir() - - sameDevice := false - if i1 != nil && i2 != nil { - si1 := i1.Sys().(*syscall.Stat_t) - si2 := i2.Sys().(*syscall.Stat_t) - if si1.Dev == si2.Dev { - sameDevice = true - } - } - - // If these files are both non-existent, or leaves (non-dirs), we are done. - if !is1Dir && !is2Dir { - return nil - } - - // Fetch the names of all the files contained in both directories being walked: - var names1, names2 []nameIno - if is1Dir { - names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access - if err != nil { - return err - } - } - if is2Dir { - names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access - if err != nil { - return err - } - } - - // We have lists of the files contained in both parallel directories, sorted - // in the same order. Walk them in parallel, generating a unique merged list - // of all items present in either or both directories. - var names []string - ix1 := 0 - ix2 := 0 - - for ix1 < len(names1) && ix2 < len(names2) { - ni1 := names1[ix1] - ni2 := names2[ix2] - - switch strings.Compare(ni1.name, ni2.name) { - case -1: // ni1 < ni2 -- advance ni1 - // we will not encounter ni1 in names2 - names = append(names, ni1.name) - ix1++ - case 0: // ni1 == ni2 - if ni1.ino != ni2.ino || !sameDevice { - names = append(names, ni1.name) - } - ix1++ - ix2++ - case 1: // ni1 > ni2 -- advance ni2 - // we will not encounter ni2 in names1 - names = append(names, ni2.name) - ix2++ - } - } - for ix1 < len(names1) { - names = append(names, names1[ix1].name) - ix1++ - } - for ix2 < len(names2) { - names = append(names, names2[ix2].name) - ix2++ - } - - // For each of the names present in either or both of the directories being - // iterated, stat the name under each root, and recurse the pair of them: - for _, name := range names { - fname := filepath.Join(path, name) - var cInfo1, cInfo2 os.FileInfo - if is1Dir { - cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if is2Dir { - cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if err = w.walk(fname, cInfo1, cInfo2); err != nil { - return err - } - } - return nil -} - -// {name,inode} pairs used to support the early-pruning logic of the walker type -type nameIno struct { - name string - ino uint64 -} - -type nameInoSlice []nameIno - -func (s nameInoSlice) Len() int { return len(s) } -func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name } - -// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode -// numbers further up the stack when reading directory contents. Unlike -// os.Readdirnames, which returns a list of filenames, this function returns a -// list of {filename,inode} pairs. -func readdirnames(dirname string) (names []nameIno, err error) { - var ( - size = 100 - buf = make([]byte, 4096) - nbuf int - bufp int - nb int - ) - - f, err := os.Open(dirname) - if err != nil { - return nil, err - } - defer f.Close() - - names = make([]nameIno, 0, size) // Empty with room to grow. - for { - // Refill the buffer if necessary - if bufp >= nbuf { - bufp = 0 - nbuf, err = unix.ReadDirent(int(f.Fd()), buf) // getdents on linux - if nbuf < 0 { - nbuf = 0 - } - if err != nil { - return nil, os.NewSyscallError("readdirent", err) - } - if nbuf <= 0 { - break // EOF - } - } - - // Drain the buffer - nb, names = parseDirent(buf[bufp:nbuf], names) - bufp += nb - } - - sl := nameInoSlice(names) - sort.Sort(sl) - return sl, nil -} - -// parseDirent is a minor modification of unix.ParseDirent (linux version) -// which returns {name,inode} pairs instead of just names. -func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) { - origlen := len(buf) - for len(buf) > 0 { - dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - buf = buf[dirent.Reclen:] - if dirent.Ino == 0 { // File absent in directory. - continue - } - b := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - name := string(b[0:clen(b[:])]) - if name == "." || name == ".." { // Useless names - continue - } - names = append(names, nameIno{name, dirent.Ino}) - } - return origlen - len(buf), names -} - -func clen(n []byte) int { - for i := 0; i < len(n); i++ { - if n[i] == 0 { - return i - } - } - return len(n) -} diff --git a/vendor/github.com/moby/go-archive/changes_other.go b/vendor/github.com/moby/go-archive/changes_other.go deleted file mode 100644 index a8a3a5a6faa8..000000000000 --- a/vendor/github.com/moby/go-archive/changes_other.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build !linux - -package archive - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" -) - -func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) { - var ( - oldRoot, newRoot *FileInfo - err1, err2 error - errs = make(chan error, 2) - ) - go func() { - oldRoot, err1 = collectFileInfo(oldDir) - errs <- err1 - }() - go func() { - newRoot, err2 = collectFileInfo(newDir) - errs <- err2 - }() - - // block until both routines have returned - for i := 0; i < 2; i++ { - if err := <-errs; err != nil { - return nil, nil, err - } - } - - return oldRoot, newRoot, nil -} - -func collectFileInfo(sourceDir string) (*FileInfo, error) { - root := newRootFileInfo() - - err := filepath.WalkDir(sourceDir, func(path string, _ os.DirEntry, err error) error { - if err != nil { - return err - } - - // Rebase path - relPath, err := filepath.Rel(sourceDir, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - relPath = filepath.Join(string(os.PathSeparator), relPath) - - // See https://github.com/golang/go/issues/9168 - bug in filepath.Join. - // Temporary workaround. If the returned path starts with two backslashes, - // trim it down to a single backslash. Only relevant on Windows. - if runtime.GOOS == "windows" { - if strings.HasPrefix(relPath, `\\`) { - relPath = relPath[1:] - } - } - - if relPath == string(os.PathSeparator) { - return nil - } - - parent := root.LookUp(filepath.Dir(relPath)) - if parent == nil { - return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) - } - - s, err := os.Lstat(path) - if err != nil { - return err - } - - info := &FileInfo{ - name: filepath.Base(relPath), - children: make(map[string]*FileInfo), - parent: parent, - stat: s, - } - - info.capability, _ = lgetxattr(path, "security.capability") - - parent.children[info.name] = info - - return nil - }) - if err != nil { - return nil, err - } - return root, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_unix.go b/vendor/github.com/moby/go-archive/changes_unix.go deleted file mode 100644 index 4dd98bd2935f..000000000000 --- a/vendor/github.com/moby/go-archive/changes_unix.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !windows - -package archive - -import ( - "io/fs" - "os" - "syscall" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - oldSys := oldStat.Sys().(*syscall.Stat_t) - newSys := newStat.Sys().(*syscall.Stat_t) - // Don't look at size for dirs, its not a good measure of change - if oldStat.Mode() != newStat.Mode() || - oldSys.Uid != newSys.Uid || - oldSys.Gid != newSys.Gid || - oldSys.Rdev != newSys.Rdev || - // Don't look at size or modification time for dirs, its not a good - // measure of change. See https://github.com/moby/moby/issues/9874 - // for a description of the issue with modification time, and - // https://github.com/moby/moby/pull/11422 for the change. - // (Note that in the Windows implementation of this function, - // modification time IS taken as a change). See - // https://github.com/moby/moby/pull/37982 for more information. - (!oldStat.Mode().IsDir() && - (!sameFsTime(oldStat.ModTime(), newStat.ModTime()) || (oldStat.Size() != newStat.Size()))) { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) uint64 { - return fi.Sys().(*syscall.Stat_t).Ino -} - -func hasHardlinks(fi os.FileInfo) bool { - return fi.Sys().(*syscall.Stat_t).Nlink > 1 -} diff --git a/vendor/github.com/moby/go-archive/changes_windows.go b/vendor/github.com/moby/go-archive/changes_windows.go deleted file mode 100644 index c89605c78fed..000000000000 --- a/vendor/github.com/moby/go-archive/changes_windows.go +++ /dev/null @@ -1,33 +0,0 @@ -package archive - -import ( - "io/fs" - "os" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - // Note there is slight difference between the Linux and Windows - // implementations here. Due to https://github.com/moby/moby/issues/9874, - // and the fix at https://github.com/moby/moby/pull/11422, Linux does not - // consider a change to the directory time as a change. Windows on NTFS - // does. See https://github.com/moby/moby/pull/37982 for more information. - - if !sameFsTime(oldStat.ModTime(), newStat.ModTime()) || - oldStat.Mode() != newStat.Mode() || - oldStat.Size() != newStat.Size() && !oldStat.Mode().IsDir() { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) (inode uint64) { - return -} - -func hasHardlinks(fi os.FileInfo) bool { - return false -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive.go b/vendor/github.com/moby/go-archive/chrootarchive/archive.go deleted file mode 100644 index 26da8e2924ba..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive.go +++ /dev/null @@ -1,98 +0,0 @@ -package chrootarchive - -import ( - "errors" - "io" - "os" - "path/filepath" - - "github.com/moby/sys/user" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// NewArchiver returns a new Archiver which uses chrootarchive.Untar -func NewArchiver(idMapping user.IdentityMapping) *archive.Archiver { - return &archive.Archiver{ - Untar: Untar, - IDMapping: idMapping, - } -} - -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive may be compressed with one of the following algorithms: -// identity (uncompressed), gzip, bzip2, xz. -func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return untarHandler(tarArchive, dest, options, true, dest) -} - -// UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory -// The root directory is the directory that will be chrooted to. -// `dest` must be a path within `root`, if it is not an error will be returned. -// -// `root` should set to a directory which is not controlled by any potentially -// malicious process. -// -// This should be used to prevent a potential attacker from manipulating `dest` -// such that it would provide access to files outside of `dest` through things -// like symlinks. Normally `ResolveSymlinksInScope` would handle this, however -// sanitizing symlinks in this manner is inherently racey: -// ref: CVE-2018-15664 -func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error { - return untarHandler(tarArchive, dest, options, true, root) -} - -// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive must be an uncompressed stream. -func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return untarHandler(tarArchive, dest, options, false, dest) -} - -// Handler for teasing out the automatic decompression -func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error { - if tarArchive == nil { - return errors.New("empty archive") - } - 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. - if dest == root { - uid, gid := options.IDMap.RootPair() - - dest = filepath.Clean(dest) - if _, err := os.Stat(dest); os.IsNotExist(err) { - if err := user.MkdirAllAndChown(dest, 0o755, uid, gid, user.WithOnlyNew); err != nil { - return err - } - } - } - - r := io.NopCloser(tarArchive) - if decompress { - decompressedArchive, err := compression.DecompressStream(tarArchive) - if err != nil { - return err - } - defer decompressedArchive.Close() - r = decompressedArchive - } - - return invokeUnpack(r, dest, options, root) -} - -// Tar tars the requested path while chrooted to the specified root. -func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - if options == nil { - options = &archive.TarOptions{} - } - return invokePack(srcPath, options, root) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go deleted file mode 100644 index a3b73676d219..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go +++ /dev/null @@ -1,54 +0,0 @@ -package chrootarchive - -import ( - "fmt" - "io" - - "golang.org/x/sys/unix" - - "github.com/moby/go-archive" -) - -func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { - done := make(chan error) - err := goInChroot(root, func() { done <- archive.Unpack(decompressedArchive, relDest, options) }) - if err != nil { - return err - } - return <-done -} - -func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) { - tb, err := archive.NewTarballer(relSrc, options) - if err != nil { - return nil, fmt.Errorf("error processing tar file: %w", err) - } - err = goInChroot(root, tb.Do) - if err != nil { - return nil, fmt.Errorf("could not chroot: %w", err) - } - return tb.Reader(), nil -} - -func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { - type result struct { - layerSize int64 - err error - } - done := make(chan result) - - err := goInChroot(root, func() { - // We need to be able to set any perms - _ = unix.Umask(0) - - size, err := archive.UnpackLayer("/", layer, options) - done <- result{layerSize: size, err: err} - }) - if err != nil { - return 0, err - } - - res := <-done - - return res.layerSize, res.err -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go deleted file mode 100644 index e2b8d5016acf..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build !windows - -package chrootarchive - -import ( - "errors" - "io" - "path/filepath" - "strings" - - "github.com/moby/go-archive" -) - -func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error { - relDest, err := resolvePathInChroot(root, dest) - if err != nil { - return err - } - - return doUnpack(decompressedArchive, relDest, root, options) -} - -func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - relSrc, err := resolvePathInChroot(root, srcPath) - if err != nil { - return nil, err - } - - // make sure we didn't trim a trailing slash with the call to `resolvePathInChroot` - if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") { - relSrc += "/" - } - - return doPack(relSrc, root, options) -} - -// resolvePathInChroot returns the equivalent to path inside a chroot rooted at root. -// The returned path always begins with '/'. -// -// - resolvePathInChroot("/a/b", "/a/b/c/d") -> "/c/d" -// - resolvePathInChroot("/a/b", "/a/b") -> "/" -// -// The implementation is buggy, and some bugs may be load-bearing. -// Here be dragons. -func resolvePathInChroot(root, path string) (string, error) { - if root == "" { - return "", errors.New("root path must not be empty") - } - rel, err := filepath.Rel(root, path) - if err != nil { - return "", err - } - if rel == "." { - rel = "/" - } - if rel[0] != '/' { - rel = "/" + rel - } - return rel, nil -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go deleted file mode 100644 index f5b02ea55068..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go +++ /dev/null @@ -1,226 +0,0 @@ -//go:build unix && !linux - -package chrootarchive - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "syscall" - - "github.com/moby/sys/reexec" - "golang.org/x/sys/unix" - - "github.com/moby/go-archive" -) - -const ( - packCmd = "chrootarchive-pack-in-chroot" - unpackCmd = "chrootarchive-unpack-in-chroot" - unpackLayerCmd = "chrootarchive-unpack-layer-in-chroot" -) - -func init() { - reexec.Register(packCmd, reexecMain(packInChroot)) - reexec.Register(unpackCmd, reexecMain(unpackInChroot)) - reexec.Register(unpackLayerCmd, reexecMain(unpackLayerInChroot)) -} - -func reexecMain(f func(options archive.TarOptions, args ...string) error) func() { - return func() { - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "root parameter is required") - os.Exit(1) - } - - options, err := recvOptions() - root := os.Args[1] - - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - if err := syscall.Chroot(root); err != nil { - fmt.Fprintln( - os.Stderr, - os.PathError{Op: "chroot", Path: root, Err: err}, - ) - os.Exit(2) - } - - if err := f(*options, os.Args[2:]...); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(3) - } - } -} - -func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { - optionsR, optionsW, err := os.Pipe() - if err != nil { - return err - } - defer optionsW.Close() - defer optionsR.Close() - - stderr := bytes.NewBuffer(nil) - - cmd := reexec.Command(unpackCmd, root, relDest) - cmd.Stdin = decompressedArchive - cmd.Stderr = stderr - cmd.ExtraFiles = []*os.File{ - optionsR, - } - - if err = cmd.Start(); err != nil { - return fmt.Errorf("re-exec error: %w", err) - } - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return fmt.Errorf("tar options encoding failed: %w", err) - } - - if err = cmd.Wait(); err != nil { - return fmt.Errorf("%s: %w", stderr.String(), err) - } - - return nil -} - -func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) { - optionsR, optionsW, err := os.Pipe() - if err != nil { - return nil, err - } - defer optionsW.Close() - defer optionsR.Close() - - stderr := bytes.NewBuffer(nil) - cmd := reexec.Command(packCmd, root, relSrc) - cmd.ExtraFiles = []*os.File{ - optionsR, - } - cmd.Stderr = stderr - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - r, w := io.Pipe() - - if err = cmd.Start(); err != nil { - return nil, fmt.Errorf("re-exec error: %w", err) - } - - go func() { - _, _ = 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)) - } else { - r.Close() - } - }() - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return nil, fmt.Errorf("tar options encoding failed: %w", err) - } - - return r, nil -} - -func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { - var result int64 - optionsR, optionsW, err := os.Pipe() - if err != nil { - return 0, err - } - defer optionsW.Close() - defer optionsR.Close() - buffer := bytes.NewBuffer(nil) - - cmd := reexec.Command(unpackLayerCmd, root) - cmd.Stdin = layer - cmd.Stdout = buffer - cmd.Stderr = buffer - cmd.ExtraFiles = []*os.File{ - optionsR, - } - - if err = cmd.Start(); err != nil { - return 0, fmt.Errorf("re-exec error: %w", err) - } - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return 0, fmt.Errorf("tar options encoding failed: %w", err) - } - - if err = cmd.Wait(); err != nil { - return 0, fmt.Errorf("%s: %w", buffer.String(), err) - } - - if err = json.NewDecoder(buffer).Decode(&result); err != nil { - return 0, fmt.Errorf("json decoding error: %w", err) - } - - return result, nil -} - -func unpackInChroot(options archive.TarOptions, args ...string) error { - if len(args) < 1 { - return fmt.Errorf("destination parameter is required") - } - - relDest := args[0] - - return archive.Unpack(os.Stdin, relDest, &options) -} - -func packInChroot(options archive.TarOptions, args ...string) error { - if len(args) < 1 { - return fmt.Errorf("source parameter is required") - } - - relSrc := args[0] - - tb, err := archive.NewTarballer(relSrc, &options) - if err != nil { - return err - } - - go tb.Do() - - _, err = io.Copy(os.Stdout, tb.Reader()) - - return err -} - -func unpackLayerInChroot(options archive.TarOptions, _args ...string) error { - // We need to be able to set any perms - _ = unix.Umask(0) - - size, err := archive.UnpackLayer("/", os.Stdin, &options) - if err != nil { - return err - } - - return json.NewEncoder(os.Stdout).Encode(size) -} - -func recvOptions() (*archive.TarOptions, error) { - var options archive.TarOptions - optionsPipe := os.NewFile(3, "tar-options") - if optionsPipe == nil { - return nil, fmt.Errorf("could not read tar options from the pipe") - } - defer optionsPipe.Close() - err := json.NewDecoder(optionsPipe).Decode(&options) - if err != nil { - return &options, err - } - - return &options, nil -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go deleted file mode 100644 index 078341f57f95..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go +++ /dev/null @@ -1,40 +0,0 @@ -package chrootarchive - -import ( - "io" - "strings" - - "github.com/moby/go-archive" -) - -// longPathPrefix is the longpath prefix for Windows file paths. -const longPathPrefix = `\\?\` - -// 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. -// -// addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. -func addLongPathPrefix(srcPath string) string { - if strings.HasPrefix(srcPath, longPathPrefix) { - return srcPath - } - if strings.HasPrefix(srcPath, `\\`) { - // This is a UNC path, so we need to add 'UNC' to the path as well. - return longPathPrefix + `UNC` + srcPath[1:] - } - return longPathPrefix + srcPath -} - -func invokeUnpack(decompressedArchive io.ReadCloser, dest string, options *archive.TarOptions, root string) error { - // Windows is different to Linux here because Windows does not support - // chroot. Hence there is no point sandboxing a chrooted process to - // do the unpack. We call inline instead within the daemon process. - return archive.Unpack(decompressedArchive, addLongPathPrefix(dest), options) -} - -func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - // Windows is different to Linux here because Windows does not support - // chroot. Hence there is no point sandboxing a chrooted process to - // do the pack. We call inline instead within the daemon process. - return archive.TarWithOptions(srcPath, options) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go b/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go deleted file mode 100644 index 3d9790be4676..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go +++ /dev/null @@ -1,35 +0,0 @@ -package chrootarchive - -import ( - "github.com/moby/sys/mount" - "golang.org/x/sys/unix" - - "github.com/moby/go-archive/internal/mounttree" - "github.com/moby/go-archive/internal/unshare" -) - -// goInChroot starts fn in a goroutine where the root directory, current working -// directory and umask are unshared from other goroutines and the root directory -// has been changed to path. These changes are only visible to the goroutine in -// which fn is executed. Any other goroutines, including ones started from fn, -// will see the same root directory and file system attributes as the rest of -// the process. -func goInChroot(path string, fn func()) error { - return unshare.Go( - unix.CLONE_FS|unix.CLONE_NEWNS, - func() error { - // Make everything in new ns slave. - // Don't use `private` here as this could race where the mountns gets a - // reference to a mount and an unmount from the host does not propagate, - // which could potentially cause transient errors for other operations, - // even though this should be relatively small window here `slave` should - // not cause any problems. - if err := mount.MakeRSlave("/"); err != nil { - return err - } - - return mounttree.SwitchRoot(path) - }, - fn, - ) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff.go b/vendor/github.com/moby/go-archive/chrootarchive/diff.go deleted file mode 100644 index c09fa5b0d0d4..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff.go +++ /dev/null @@ -1,23 +0,0 @@ -package chrootarchive - -import ( - "io" - - "github.com/moby/go-archive" -) - -// ApplyLayer parses a diff in the standard layer format from `layer`, -// and applies it to the directory `dest`. The stream `layer` can only be -// uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer io.Reader) (size int64, err error) { - return applyLayerHandler(dest, layer, &archive.TarOptions{}, true) -} - -// ApplyUncompressedLayer parses a diff in the standard layer format from -// `layer`, and applies it to the directory `dest`. The stream `layer` -// can only be uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer io.Reader, options *archive.TarOptions) (int64, error) { - return applyLayerHandler(dest, layer, options, false) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go b/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go deleted file mode 100644 index 0e95bcd95bc3..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build !windows - -package chrootarchive - -import ( - "io" - "path/filepath" - - "github.com/moby/sys/userns" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// applyLayerHandler parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. -func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, _ error) { - if decompress { - decompressed, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompressed.Close() - - layer = decompressed - } - if options == nil { - options = &archive.TarOptions{} - } - if userns.RunningInUserNS() { - options.InUserNS = true - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - dest = filepath.Clean(dest) - return doUnpackLayer(dest, layer, options) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go b/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go deleted file mode 100644 index 7e0cb5490f14..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go +++ /dev/null @@ -1,34 +0,0 @@ -package chrootarchive - -import ( - "fmt" - "io" - "path/filepath" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// applyLayerHandler parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. -func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) { - if decompress { - decompressed, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompressed.Close() - - layer = decompressed - } - - // Ensure it is a Windows-style volume path - dest = addLongPathPrefix(filepath.Clean(dest)) - s, err := archive.UnpackLayer(dest, layer, nil) - if err != nil { - return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s: %w", layer, dest, err) - } - - return s, nil -} diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go deleted file mode 100644 index e298cefb3249..000000000000 --- a/vendor/github.com/moby/go-archive/compression/compression.go +++ /dev/null @@ -1,263 +0,0 @@ -package compression - -import ( - "bufio" - "bytes" - "compress/bzip2" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "strconv" - "sync" - - "github.com/containerd/log" - "github.com/klauspost/compress/zstd" -) - -// Compression is the state represents if compressed or not. -type Compression int - -const ( - None Compression = 0 // None represents the uncompressed. - Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm. - Gzip Compression = 2 // Gzip is gzip compression algorithm. - Xz Compression = 3 // Xz is xz compression algorithm. - Zstd Compression = 4 // Zstd is zstd compression algorithm. -) - -// Extension returns the extension of a file that uses the specified compression algorithm. -func (c *Compression) Extension() string { - switch *c { - case None: - return "tar" - case Bzip2: - return "tar.bz2" - case Gzip: - return "tar.gz" - case Xz: - return "tar.xz" - case Zstd: - return "tar.zst" - default: - return "" - } -} - -type readCloserWrapper struct { - io.Reader - closer func() error -} - -func (r *readCloserWrapper) Close() error { - if r.closer != nil { - return r.closer() - } - return nil -} - -type nopWriteCloser struct { - io.Writer -} - -func (nopWriteCloser) Close() error { return nil } - -var bufioReader32KPool = &sync.Pool{ - New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, -} - -type bufferedReader struct { - buf *bufio.Reader -} - -func newBufferedReader(r io.Reader) *bufferedReader { - buf := bufioReader32KPool.Get().(*bufio.Reader) - buf.Reset(r) - return &bufferedReader{buf} -} - -func (r *bufferedReader) Read(p []byte) (int, error) { - if r.buf == nil { - return 0, io.EOF - } - n, err := r.buf.Read(p) - if errors.Is(err, io.EOF) { - r.buf.Reset(nil) - bufioReader32KPool.Put(r.buf) - r.buf = nil - } - return n, err -} - -func (r *bufferedReader) Peek(n int) ([]byte, error) { - if r.buf == nil { - return nil, io.EOF - } - return r.buf.Peek(n) -} - -// DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. -func DecompressStream(archive io.Reader) (io.ReadCloser, error) { - buf := newBufferedReader(archive) - bs, err := buf.Peek(10) - if err != nil && !errors.Is(err, io.EOF) { - // Note: we'll ignore any io.EOF error because there are some odd - // cases where the layer.tar file will be empty (zero bytes) and - // that results in an io.EOF from the Peek() call. So, in those - // cases we'll just treat it as a non-compressed stream and - // that means just create an empty layer. - // See Issue 18170 - return nil, err - } - - switch compression := Detect(bs); compression { - case None: - return &readCloserWrapper{ - Reader: buf, - }, nil - case Gzip: - ctx, cancel := context.WithCancel(context.Background()) - gzReader, err := gzipDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - - return &readCloserWrapper{ - Reader: gzReader, - closer: func() error { - cancel() - return gzReader.Close() - }, - }, nil - case Bzip2: - bz2Reader := bzip2.NewReader(buf) - return &readCloserWrapper{ - Reader: bz2Reader, - }, nil - case Xz: - ctx, cancel := context.WithCancel(context.Background()) - - xzReader, err := xzDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - - return &readCloserWrapper{ - Reader: xzReader, - closer: func() error { - cancel() - return xzReader.Close() - }, - }, nil - case Zstd: - zstdReader, err := zstd.NewReader(buf) - if err != nil { - return nil, err - } - return &readCloserWrapper{ - Reader: zstdReader, - closer: func() error { - zstdReader.Close() - return nil - }, - }, nil - - default: - return nil, fmt.Errorf("unsupported compression format (%d)", compression) - } -} - -// CompressStream compresses the dest with specified compression algorithm. -func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { - switch compression { - case None: - return nopWriteCloser{dest}, nil - case Gzip: - return gzip.NewWriter(dest), nil - case Bzip2: - // archive/bzip2 does not support writing. - return nil, errors.New("unsupported compression format: tar.bz2") - case Xz: - // there is no xz support at all - // However, this is not a problem as docker only currently generates gzipped tars - return nil, errors.New("unsupported compression format: tar.xz") - default: - return nil, fmt.Errorf("unsupported compression format (%d)", compression) - } -} - -func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { - args := []string{"xz", "-d", "-c", "-q"} - - return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) -} - -func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { - if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { - noPigz, err := strconv.ParseBool(noPigzEnv) - if err != nil { - log.G(ctx).WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") - } - if noPigz { - log.G(ctx).Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) - return gzip.NewReader(buf) - } - } - - unpigzPath, err := exec.LookPath("unpigz") - if err != nil { - log.G(ctx).Debugf("unpigz binary not found, falling back to go gzip library") - return gzip.NewReader(buf) - } - - log.G(ctx).Debugf("Using %s to decompress", unpigzPath) - - return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) -} - -// cmdStream executes a command, and returns its stdout as a stream. -// If the command fails to run or doesn't complete successfully, an error -// will be returned, including anything written on stderr. -func cmdStream(cmd *exec.Cmd, in io.Reader) (io.ReadCloser, error) { - reader, writer := io.Pipe() - - cmd.Stdin = in - cmd.Stdout = writer - - var errBuf bytes.Buffer - cmd.Stderr = &errBuf - - // Run the command and return the pipe - if err := cmd.Start(); err != nil { - return nil, err - } - - // Ensure the command has exited before we clean anything up - done := make(chan struct{}) - - // Copy stdout to the returned pipe - go func() { - if err := cmd.Wait(); err != nil { - _ = writer.CloseWithError(fmt.Errorf("%w: %s", err, errBuf.String())) - } else { - _ = writer.Close() - } - close(done) - }() - - return &readCloserWrapper{ - Reader: reader, - closer: func() error { - // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as - // cmd.Wait waits for any non-file stdout/stderr/stdin to close. - err := reader.Close() - <-done - return err - }, - }, nil -} diff --git a/vendor/github.com/moby/go-archive/compression/compression_detect.go b/vendor/github.com/moby/go-archive/compression/compression_detect.go deleted file mode 100644 index 85eda927ccfb..000000000000 --- a/vendor/github.com/moby/go-archive/compression/compression_detect.go +++ /dev/null @@ -1,65 +0,0 @@ -package compression - -import ( - "bytes" - "encoding/binary" -) - -const ( - zstdMagicSkippableStart = 0x184D2A50 - zstdMagicSkippableMask = 0xFFFFFFF0 -) - -var ( - bzip2Magic = []byte{0x42, 0x5A, 0x68} - gzipMagic = []byte{0x1F, 0x8B, 0x08} - xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} - zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} -) - -type matcher = func([]byte) bool - -// Detect detects the compression algorithm of the source. -func Detect(source []byte) Compression { - compressionMap := map[Compression]matcher{ - Bzip2: magicNumberMatcher(bzip2Magic), - Gzip: magicNumberMatcher(gzipMagic), - Xz: magicNumberMatcher(xzMagic), - Zstd: zstdMatcher(), - } - for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { - fn := compressionMap[compression] - if fn(source) { - return compression - } - } - return None -} - -func magicNumberMatcher(m []byte) matcher { - return func(source []byte) bool { - return bytes.HasPrefix(source, m) - } -} - -// zstdMatcher detects zstd compression algorithm. -// Zstandard compressed data is made of one or more frames. -// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. -// See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details. -func zstdMatcher() matcher { - return func(source []byte) bool { - if bytes.HasPrefix(source, zstdMagic) { - // Zstandard frame - return true - } - // skippable frame - if len(source) < 8 { - return false - } - // magic number from 0x184D2A50 to 0x184D2A5F. - if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { - return true - } - return false - } -} diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go deleted file mode 100644 index 77d038c42310..000000000000 --- a/vendor/github.com/moby/go-archive/copy.go +++ /dev/null @@ -1,496 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "io" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/containerd/log" -) - -// Errors used or returned by this file. -var ( - ErrNotDirectory = errors.New("not a directory") - ErrDirNotExists = errors.New("no such directory") - ErrCannotCopyDir = errors.New("cannot copy directory") - ErrInvalidCopySource = errors.New("invalid copy source content") -) - -var copyPool = sync.Pool{ - New: func() interface{} { s := make([]byte, 32*1024); return &s }, -} - -func copyWithBuffer(dst io.Writer, src io.Reader) error { - buf := copyPool.Get().(*[]byte) - _, err := io.CopyBuffer(dst, src, *buf) - copyPool.Put(buf) - return err -} - -// PreserveTrailingDotOrSeparator returns the given cleaned path (after -// processing using any utility functions from the path or filepath stdlib -// packages) and appends a trailing `/.` or `/` if its corresponding original -// path (from before being processed by utility functions from the path or -// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned -// path already ends in a `.` path segment, then another is not added. If the -// clean path already ends in a path separator, then another is not added. -func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string) string { - // Ensure paths are in platform semantics - cleanedPath = normalizePath(cleanedPath) - originalPath = normalizePath(originalPath) - - if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) { - if !hasTrailingPathSeparator(cleanedPath) { - // Add a separator if it doesn't already end with one (a cleaned - // path would only end in a separator if it is the root). - cleanedPath += string(filepath.Separator) - } - cleanedPath += "." - } - - if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) { - cleanedPath += string(filepath.Separator) - } - - return cleanedPath -} - -// assertsDirectory returns whether the given path is -// asserted to be a directory, i.e., the path ends with -// a trailing '/' or `/.`, assuming a path separator of `/`. -func assertsDirectory(path string) bool { - return hasTrailingPathSeparator(path) || specifiesCurrentDir(path) -} - -// hasTrailingPathSeparator returns whether the given -// path ends with the system's path separator character. -func hasTrailingPathSeparator(path string) bool { - return len(path) > 0 && path[len(path)-1] == filepath.Separator -} - -// specifiesCurrentDir returns whether the given path specifies -// a "current directory", i.e., the last path segment is `.`. -func specifiesCurrentDir(path string) bool { - return filepath.Base(path) == "." -} - -// SplitPathDirEntry splits the given path between its directory name and its -// basename by first cleaning the path but preserves a trailing "." if the -// original path specified the current directory. -func SplitPathDirEntry(path string) (dir, base string) { - cleanedPath := filepath.Clean(filepath.FromSlash(path)) - - if specifiesCurrentDir(path) { - cleanedPath += string(os.PathSeparator) + "." - } - - return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) -} - -// TarResource archives the resource described by the given CopyInfo to a Tar -// archive. A non-nil error is returned if sourcePath does not exist or is -// asserted to be a directory but exists as another type of file. -// -// This function acts as a convenient wrapper around TarWithOptions, which -// requires a directory as the source path. TarResource accepts either a -// directory or a file path and correctly sets the Tar options. -func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) { - return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) -} - -// TarResourceRebase is like TarResource but renames the first path element of -// items in the resulting tar archive to match the given rebaseName if not "". -func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ error) { - sourcePath = normalizePath(sourcePath) - if _, err := os.Lstat(sourcePath); err != nil { - // Catches the case where the source does not exist or is not a - // directory if asserted to be a directory, as this also causes an - // error. - return nil, err - } - - // Separate the source path between its directory and - // the entry in that directory which we are archiving. - sourceDir, sourceBase := SplitPathDirEntry(sourcePath) - opts := TarResourceRebaseOpts(sourceBase, rebaseName) - - log.G(context.TODO()).Debugf("copying %q from %q", sourceBase, sourceDir) - return TarWithOptions(sourceDir, opts) -} - -// TarResourceRebaseOpts does not preform the Tar, but instead just creates the rebase -// parameters to be sent to TarWithOptions (the TarOptions struct) -func TarResourceRebaseOpts(sourceBase string, rebaseName string) *TarOptions { - filter := []string{sourceBase} - return &TarOptions{ - IncludeFiles: filter, - IncludeSourceDir: true, - RebaseNames: map[string]string{ - sourceBase: rebaseName, - }, - } -} - -// CopyInfo holds basic info about the source -// or destination path of a copy operation. -type CopyInfo struct { - Path string - Exists bool - IsDir bool - RebaseName string -} - -// CopyInfoSourcePath stats the given path to create a CopyInfo -// struct representing that resource for the source of an archive copy -// operation. The given path should be an absolute local path. A source path -// has all symlinks evaluated that appear before the last path separator ("/" -// on Unix). As it is to be a copy source, the path must exist. -func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) { - // normalize the file path and then evaluate the symbol link - // we will use the target file instead of the symbol link if - // followLink is set - path = normalizePath(path) - - resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink) - if err != nil { - return CopyInfo{}, err - } - - stat, err := os.Lstat(resolvedPath) - if err != nil { - return CopyInfo{}, err - } - - return CopyInfo{ - Path: resolvedPath, - Exists: true, - IsDir: stat.IsDir(), - RebaseName: rebaseName, - }, nil -} - -// CopyInfoDestinationPath stats the given path to create a CopyInfo -// struct representing that resource for the destination of an archive copy -// operation. The given path should be an absolute local path. -func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { - maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. - path = normalizePath(path) - originalPath := path - - stat, err := os.Lstat(path) - - if err == nil && stat.Mode()&os.ModeSymlink == 0 { - // The path exists and is not a symlink. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil - } - - // While the path is a symlink. - for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { - if n > maxSymlinkIter { - // Don't follow symlinks more than this arbitrary number of times. - return CopyInfo{}, errors.New("too many symlinks in " + originalPath) - } - - // The path is a symbolic link. We need to evaluate it so that the - // destination of the copy operation is the link target and not the - // link itself. This is notably different than CopyInfoSourcePath which - // only evaluates symlinks before the last appearing path separator. - // Also note that it is okay if the last path element is a broken - // symlink as the copy operation should create the target. - var linkTarget string - - linkTarget, err = os.Readlink(path) - if err != nil { - return CopyInfo{}, err - } - - if !filepath.IsAbs(linkTarget) { - // Join with the parent directory. - dstParent, _ := SplitPathDirEntry(path) - linkTarget = filepath.Join(dstParent, linkTarget) - } - - path = linkTarget - stat, err = os.Lstat(path) - } - - if err != nil { - // It's okay if the destination path doesn't exist. We can still - // continue the copy operation if the parent directory exists. - if !os.IsNotExist(err) { - return CopyInfo{}, err - } - - // Ensure destination parent dir exists. - dstParent, _ := SplitPathDirEntry(path) - - parentDirStat, err := os.Stat(dstParent) - if err != nil { - return CopyInfo{}, err - } - if !parentDirStat.IsDir() { - return CopyInfo{}, ErrNotDirectory - } - - return CopyInfo{Path: path}, nil - } - - // The path exists after resolving symlinks. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil -} - -// PrepareArchiveCopy prepares the given srcContent archive, which should -// contain the archived resource described by srcInfo, to the destination -// described by dstInfo. Returns the possibly modified content archive along -// with the path to the destination directory which it should be extracted to. -func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content io.ReadCloser, err error) { - // Ensure in platform semantics - srcInfo.Path = normalizePath(srcInfo.Path) - dstInfo.Path = normalizePath(dstInfo.Path) - - // Separate the destination path between its directory and base - // components in case the source archive contents need to be rebased. - dstDir, dstBase := SplitPathDirEntry(dstInfo.Path) - _, srcBase := SplitPathDirEntry(srcInfo.Path) - - switch { - case dstInfo.Exists && dstInfo.IsDir: - // The destination exists as a directory. No alteration - // to srcContent is needed as its contents can be - // simply extracted to the destination directory. - return dstInfo.Path, io.NopCloser(srcContent), nil - case dstInfo.Exists && srcInfo.IsDir: - // The destination exists as some type of file and the source - // content is a directory. This is an error condition since - // you cannot copy a directory to an existing file location. - return "", nil, ErrCannotCopyDir - case dstInfo.Exists: - // The destination exists as some type of file and the source content - // is also a file. The source content entry will have to be renamed to - // have a basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case srcInfo.IsDir: - // The destination does not exist and the source content is an archive - // of a directory. The archive should be extracted to the parent of - // the destination path instead, and when it is, the directory that is - // created as a result should take the name of the destination path. - // The source content entries will have to be renamed to have a - // basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case assertsDirectory(dstInfo.Path): - // The destination does not exist and is asserted to be created as a - // directory, but the source content is not a directory. This is an - // error condition since you cannot create a directory from a file - // source. - return "", nil, ErrDirNotExists - default: - // The last remaining case is when the destination does not exist, is - // not asserted to be a directory, and the source content is not an - // archive of a directory. It this case, the destination file will need - // to be created when the archive is extracted and the source content - // entry will have to be renamed to have a basename which matches the - // destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - } -} - -// 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) { - // 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. - oldBase = "" - } - - rebased, w := io.Pipe() - - go func() { - srcTar := tar.NewReader(srcContent) - rebasedTar := tar.NewWriter(w) - - for { - hdr, err := srcTar.Next() - if errors.Is(err, io.EOF) { - // Signals end of archive. - rebasedTar.Close() - w.Close() - return - } - if err != nil { - w.CloseWithError(err) - return - } - - // srcContent tar stream, as served by TarWithOptions(), is - // definitely in PAX format, but tar.Next() mistakenly guesses it - // as USTAR, which creates a problem: if the newBase is >100 - // characters long, WriteHeader() returns an error like - // "archive/tar: cannot encode header: Format specifies USTAR; and USTAR cannot encode Name=...". - // - // To fix, set the format to PAX here. See docker/for-linux issue #484. - hdr.Format = tar.FormatPAX - hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1) - if hdr.Typeflag == tar.TypeLink { - hdr.Linkname = strings.Replace(hdr.Linkname, oldBase, newBase, 1) - } - - if err = rebasedTar.WriteHeader(hdr); err != nil { - w.CloseWithError(err) - return - } - - // Ignoring GoSec G110. See https://github.com/securego/gosec/pull/433 - // and https://cure53.de/pentest-report_opa.pdf, which recommends to - // replace io.Copy with io.CopyN7. The latter allows to specify the - // maximum number of bytes that should be read. By properly defining - // the limit, it can be assured that a GZip compression bomb cannot - // easily cause a Denial-of-Service. - // After reviewing with @tonistiigi and @cpuguy83, this should not - // affect us, because here we do not read into memory, hence should - // 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) - return - } - } - }() - - return rebased -} - -// CopyResource performs an archive copy from the given source path to the -// given destination path. The source path MUST exist and the destination -// path's parent directory must exist. -func CopyResource(srcPath, dstPath string, followLink bool) error { - var ( - srcInfo CopyInfo - err error - ) - - // Ensure in platform semantics - srcPath = normalizePath(srcPath) - dstPath = normalizePath(dstPath) - - // Clean the source and destination paths. - srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) - dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) - - if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil { - return err - } - - content, err := TarResource(srcInfo) - if err != nil { - return err - } - defer content.Close() - - return CopyTo(content, srcInfo, dstPath) -} - -// CopyTo handles extracting the given content whose -// entries should be sourced from srcInfo to dstPath. -func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { - // The destination path need not exist, but CopyInfoDestinationPath will - // ensure that at least the parent directory exists. - dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath)) - if err != nil { - return err - } - - dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) - if err != nil { - return err - } - defer copyArchive.Close() - - options := &TarOptions{ - NoLchown: true, - NoOverwriteDirNonDir: true, - } - - return Untar(copyArchive, dstDir, options) -} - -// ResolveHostSourcePath decides real path need to be copied with parameters such as -// whether to follow symbol link or not, if followLink is true, resolvedPath will return -// link target of any symbol link file, else it will only resolve symlink of directory -// but return symbol link file itself without resolving. -func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, _ error) { - if followLink { - var err error - resolvedPath, err = filepath.EvalSymlinks(path) - if err != nil { - return "", "", err - } - - resolvedPath, rebaseName = GetRebaseName(path, resolvedPath) - } else { - dirPath, basePath := filepath.Split(path) - - // if not follow symbol link, then resolve symbol link of parent dir - resolvedDirPath, err := filepath.EvalSymlinks(dirPath) - if err != nil { - return "", "", err - } - // resolvedDirPath will have been cleaned (no trailing path separators) so - // we can manually join it with the base path element. - resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath - if hasTrailingPathSeparator(path) && - filepath.Base(path) != filepath.Base(resolvedPath) { - rebaseName = filepath.Base(path) - } - } - return resolvedPath, rebaseName, nil -} - -// GetRebaseName normalizes and compares path and resolvedPath, -// return completed resolved path and rebased file name -func GetRebaseName(path, resolvedPath string) (string, string) { - // linkTarget will have been cleaned (no trailing path separators and dot) so - // we can manually join it with them - var rebaseName string - if specifiesCurrentDir(path) && - !specifiesCurrentDir(resolvedPath) { - resolvedPath += string(filepath.Separator) + "." - } - - if hasTrailingPathSeparator(path) && - !hasTrailingPathSeparator(resolvedPath) { - resolvedPath += string(filepath.Separator) - } - - if filepath.Base(path) != filepath.Base(resolvedPath) { - // In the case where the path had a trailing separator and a symlink - // evaluation has changed the last path component, we will need to - // rebase the name in the archive that is being copied to match the - // originally requested name. - rebaseName = filepath.Base(path) - } - return resolvedPath, rebaseName -} diff --git a/vendor/github.com/moby/go-archive/copy_unix.go b/vendor/github.com/moby/go-archive/copy_unix.go deleted file mode 100644 index f579282449af..000000000000 --- a/vendor/github.com/moby/go-archive/copy_unix.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !windows - -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.ToSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/copy_windows.go b/vendor/github.com/moby/go-archive/copy_windows.go deleted file mode 100644 index 2b775b45c4f1..000000000000 --- a/vendor/github.com/moby/go-archive/copy_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.FromSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/dev_freebsd.go b/vendor/github.com/moby/go-archive/dev_freebsd.go deleted file mode 100644 index b3068fce9110..000000000000 --- a/vendor/github.com/moby/go-archive/dev_freebsd.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, dev) -} diff --git a/vendor/github.com/moby/go-archive/dev_unix.go b/vendor/github.com/moby/go-archive/dev_unix.go deleted file mode 100644 index dffc596f93f7..000000000000 --- a/vendor/github.com/moby/go-archive/dev_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows && !freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, int(dev)) -} diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go deleted file mode 100644 index 96db972d1383..000000000000 --- a/vendor/github.com/moby/go-archive/diff.go +++ /dev/null @@ -1,261 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/containerd/log" - - "github.com/moby/go-archive/compression" -) - -// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) { - tr := tar.NewReader(layer) - - var dirs []*tar.Header - unpackedPaths := make(map[string]struct{}) - - if options == nil { - options = &TarOptions{} - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - - aufsTempdir := "" - aufsHardlinks := make(map[string]*tar.Header) - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return 0, err - } - - size += hdr.Size - - // Normalize name, for safety and for a simple is-root check - hdr.Name = filepath.Clean(hdr.Name) - - // Windows does not support filenames with colons in them. Ignore - // these files. This is not a problem though (although it might - // appear that it is). Let's suppose a client is running docker pull. - // The daemon it points to is Windows. Would it make sense for the - // client to be doing a docker pull Ubuntu for example (which has files - // with colons in the name under /usr/share/man/man3)? No, absolutely - // not as it would really only make sense that they were pulling a - // Windows image. However, for development, it is necessary to be able - // to pull Linux images which are in the repository. - // - // TODO Windows. Once the registry is aware of what images are Windows- - // specific or Linux-specific, this warning should be changed to an error - // to cater for the situation where someone does manage to upload a Linux - // image but have it tagged as Windows inadvertently. - if runtime.GOOS == "windows" { - if strings.Contains(hdr.Name, ":") { - log.G(context.TODO()).Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) - continue - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return 0, err - } - - // Skip AUFS metadata dirs - if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) { - // Regular files inside /.wh..wh.plnk can be used as hardlink targets - // We don't want this directory, but we need the files in them so that - // such hardlinks can be resolved. - if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg { - basename := filepath.Base(hdr.Name) - aufsHardlinks[basename] = hdr - if aufsTempdir == "" { - if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil { - return 0, err - } - defer os.RemoveAll(aufsTempdir) - } - if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, options); err != nil { - return 0, err - } - } - - if hdr.Name != WhiteoutOpaqueDir { - continue - } - } - // #nosec G305 -- The joined path is guarded against path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) - if err != nil { - return 0, err - } - - // Note as these operations are platform specific, so must the slash be. - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - base := filepath.Base(path) - - if strings.HasPrefix(base, WhiteoutPrefix) { - dir := filepath.Dir(path) - if base == WhiteoutOpaqueDir { - _, err := os.Lstat(dir) - if err != nil { - return 0, err - } - err = filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error { - if err != nil { - if os.IsNotExist(err) { - err = nil // parent was deleted - } - return err - } - if path == dir { - return nil - } - if _, exists := unpackedPaths[path]; !exists { - return os.RemoveAll(path) - } - return nil - }) - if err != nil { - return 0, err - } - } else { - originalBase := base[len(WhiteoutPrefix):] - originalPath := filepath.Join(dir, originalBase) - if err := os.RemoveAll(originalPath); err != nil { - return 0, err - } - } - } else { - // If path exits we almost always just want to remove and replace it. - // The only exception is when it is a directory *and* the file from - // the layer is also a directory. Then we want to merge them (i.e. - // just apply the metadata from the layer). - if fi, err := os.Lstat(path); err == nil { - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { - return 0, err - } - } - } - - srcData := io.Reader(tr) - srcHdr := hdr - - // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so - // we manually retarget these into the temporary files we extracted them into - if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) { - linkBasename := filepath.Base(hdr.Linkname) - srcHdr = aufsHardlinks[linkBasename] - if srcHdr == nil { - return 0, errors.New("invalid aufs hardlink") - } - tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) - if err != nil { - return 0, err - } - defer tmpFile.Close() - srcData = tmpFile - } - - if err := remapIDs(options.IDMap, srcHdr); err != nil { - return 0, err - } - - if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil { - return 0, err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - unpackedPaths[path] = 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 { - return 0, err - } - } - - return size, nil -} - -// ApplyLayer parses a diff in the standard layer format from `layer`, -// and applies it to the directory `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer io.Reader) (int64, error) { - return applyLayerHandler(dest, layer, &TarOptions{}, true) -} - -// ApplyUncompressedLayer parses a diff in the standard layer format from -// `layer`, and applies it to the directory `dest`. The stream `layer` -// can only be uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) { - return applyLayerHandler(dest, layer, options, false) -} - -// IsEmpty checks if the tar archive is empty (doesn't contain any entries). -func IsEmpty(rd io.Reader) (bool, error) { - decompRd, err := compression.DecompressStream(rd) - if err != nil { - return true, fmt.Errorf("failed to decompress archive: %w", err) - } - defer decompRd.Close() - - tarReader := tar.NewReader(decompRd) - if _, err := tarReader.Next(); err != nil { - if errors.Is(err, io.EOF) { - return true, nil - } - return false, fmt.Errorf("failed to read next archive header: %w", err) - } - - return false, nil -} - -// do the bulk load of ApplyLayer, but allow for not calling DecompressStream -func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) { - dest = filepath.Clean(dest) - - // We need to be able to set any perms - restore := overrideUmask(0) - defer restore() - - if decompress { - decompLayer, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompLayer.Close() - layer = decompLayer - } - return UnpackLayer(dest, layer, options) -} diff --git a/vendor/github.com/moby/go-archive/diff_unix.go b/vendor/github.com/moby/go-archive/diff_unix.go deleted file mode 100644 index 7216f2f4f97c..000000000000 --- a/vendor/github.com/moby/go-archive/diff_unix.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !windows - -package archive - -import "golang.org/x/sys/unix" - -// overrideUmask sets current process's file mode creation mask to newmask -// and returns a function to restore it. -// -// WARNING for readers stumbling upon this code. Changing umask in a multi- -// threaded environment isn't safe. Don't use this without understanding the -// risks, and don't export this function for others to use (we shouldn't even -// be using this ourself). -// -// FIXME(thaJeztah): we should get rid of these hacks if possible. -func overrideUmask(newMask int) func() { - oldMask := unix.Umask(newMask) - return func() { - unix.Umask(oldMask) - } -} diff --git a/vendor/github.com/moby/go-archive/diff_windows.go b/vendor/github.com/moby/go-archive/diff_windows.go deleted file mode 100644 index d28f5b2dfdd9..000000000000 --- a/vendor/github.com/moby/go-archive/diff_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package archive - -// overrideUmask is a no-op on windows. -func overrideUmask(newmask int) func() { - return func() {} -} diff --git a/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go b/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go deleted file mode 100644 index cc0f64da681d..000000000000 --- a/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go +++ /dev/null @@ -1,94 +0,0 @@ -package mounttree - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/moby/sys/mount" - "github.com/moby/sys/mountinfo" - "golang.org/x/sys/unix" -) - -// SwitchRoot changes path to be the root of the mount tree and changes the -// current working directory to the new root. -// -// This function bind-mounts onto path; it is the caller's responsibility to set -// the desired propagation mode of path's parent mount beforehand to prevent -// unwanted propagation into different mount namespaces. -func SwitchRoot(path string) error { - if mounted, _ := mountinfo.Mounted(path); !mounted { - if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil { - return realChroot(path) - } - } - - // setup oldRoot for pivot_root - pivotDir, err := os.MkdirTemp(path, ".pivot_root") - if err != nil { - return fmt.Errorf("error setting up pivot dir: %w", err) - } - - var mounted bool - defer func() { - if mounted { - // make sure pivotDir is not mounted before we try to remove it - if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil { - if err == nil { - err = errCleanup - } - return - } - } - - errCleanup := os.Remove(pivotDir) - // pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful - // because we already cleaned it up on failed pivot_root - if errCleanup != nil && !os.IsNotExist(errCleanup) { - errCleanup = fmt.Errorf("error cleaning up after pivot: %w", errCleanup) - if err == nil { - err = errCleanup - } - } - }() - - if err := unix.PivotRoot(path, pivotDir); err != nil { - // If pivot fails, fall back to the normal chroot after cleaning up temp dir - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("error cleaning up after failed pivot: %w", err) - } - return realChroot(path) - } - mounted = true - - // This is the new path for where the old root (prior to the pivot) has been moved to - // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - - if err := unix.Chdir("/"); err != nil { - return fmt.Errorf("error changing to new root: %w", err) - } - - // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host - if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil { - return fmt.Errorf("error making old root private after pivot: %w", err) - } - - // Now unmount the old root so it's no longer visible from the new root - if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil { - return fmt.Errorf("error while unmounting old root after pivot: %w", err) - } - mounted = false - - return nil -} - -func realChroot(path string) error { - if err := unix.Chroot(path); err != nil { - return fmt.Errorf("error after fallback to chroot: %w", err) - } - if err := unix.Chdir("/"); err != nil { - return fmt.Errorf("error changing to new root after chroot: %w", err) - } - return nil -} diff --git a/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go b/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go deleted file mode 100644 index 8491b3abc031..000000000000 --- a/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go +++ /dev/null @@ -1,173 +0,0 @@ -package unshare - -import ( - "fmt" - "os" - "runtime" - - "golang.org/x/sys/unix" -) - -func init() { - // The startup thread of a process is special in a few different ways. - // Most pertinent to the discussion at hand, any per-thread kernel state - // reflected in the /proc/[pid]/ directory for a process is taken from - // the state of the startup thread. Same goes for /proc/self/; it shows - // the state of the current process' startup thread, no matter which - // thread the files are being opened from. For most programs this is a - // distinction without a difference as the kernel state, such as the - // mount namespace and current working directory, is shared among (and - // kept synchronized across) all threads of a process. But things start - // to break down once threads start unsharing and modifying parts of - // their kernel state. - // - // The Go runtime schedules goroutines to execute on the startup thread, - // same as any other. How this could be problematic is best illustrated - // with a concrete example. Consider what happens if a call to - // Go(unix.CLONE_NEWNS, ...) spawned a goroutine which gets scheduled - // onto the startup thread. The thread's mount namespace will be - // unshared and modified. The contents of the /proc/[pid]/mountinfo file - // will then describe the mount tree of the unshared namespace, not the - // namespace of any other thread. It will remain this way until the - // process exits. (The startup thread is special in another way: exiting - // it puts the process into a "non-waitable zombie" state. To avoid this - // fate, the Go runtime parks the thread instead of exiting if a - // goroutine returns while locked to the startup thread. More - // information can be found in the Go runtime sources: - // `go doc -u -src runtime.mexit`.) The github.com/moby/sys/mountinfo - // package reads from /proc/self/mountinfo, so will read the mount tree - // for the wrong namespace if the startup thread has had its mount - // namespace unshared! The /proc/thread-self/ directory, introduced in - // Linux 3.17, is one potential solution to this problem, but every - // package which opens files in /proc/self/ would need to be updated, - // and fallbacks to /proc/self/task/[tid]/ would be required to support - // older kernels. Overlooking any reference to /proc/self/ would - // manifest as stochastically-reproducible bugs, so this is far from an - // ideal solution. - // - // Reading from /proc/self/ would not be a problem if we could prevent - // the per-thread state of the startup thread from being modified - // nondeterministically in the first place. We can accomplish this - // simply by locking the main() function to the startup thread! Doing so - // excludes any other goroutine from being scheduled on the thread. - runtime.LockOSThread() -} - -// reversibleSetnsFlags maps the unshare(2) flags whose effects can be fully -// reversed using setns(2). The values are the basenames of the corresponding -// /proc/self/task/[tid]/ns/ magic symlinks to use to save and restore the -// state. -var reversibleSetnsFlags = map[int]string{ - unix.CLONE_NEWCGROUP: "cgroup", - unix.CLONE_NEWNET: "net", - unix.CLONE_NEWUTS: "uts", - unix.CLONE_NEWPID: "pid", - unix.CLONE_NEWTIME: "time", - - // The following CLONE_NEW* flags are not included because they imply - // another, irreversible flag when used with unshare(2). - // - unix.CLONE_NEWIPC: implies CLONE_SYSVMEM - // - unix.CLONE_NEWNS: implies CLONE_FS - // - unix.CLONE_NEWUSER: implies CLONE_FS since Linux 3.9 -} - -// Go calls the given functions in a new goroutine, locked to an OS thread, -// which has had the parts of its execution state disassociated from the rest of -// the current process using [unshare(2)]. It blocks until the new goroutine has -// started and setupfn has returned. fn is only called if setupfn returns nil. A -// nil setupfn or fn is equivalent to passing a no-op function. -// -// The disassociated execution state and any changes made to it are only visible -// to the goroutine which the functions are called in. Any other goroutines, -// including ones started from the function, will see the same execution state -// as the rest of the process. -// -// The acceptable flags are documented in the [unshare(2)] Linux man-page. -// The corresponding CLONE_* constants are defined in package [unix]. -// -// # Warning -// -// This function may terminate the thread which the new goroutine executed on -// after fn returns, which could cause subprocesses started with the -// [syscall.SysProcAttr] Pdeathsig field set to be signaled before process -// termination. Any subprocess started before this function is called may be -// affected, in addition to any subprocesses started inside setupfn or fn. -// There are more details at https://go.dev/issue/27505. -// -// [unshare(2)]: https://man7.org/linux/man-pages/man2/unshare.2.html -func Go(flags int, setupfn func() error, fn func()) error { - started := make(chan error) - - maskedFlags := flags - for f := range reversibleSetnsFlags { - maskedFlags &^= f - } - isReversible := maskedFlags == 0 - - go func() { - // Prepare to manipulate per-thread kernel state. - runtime.LockOSThread() - - // Not all changes to the execution state can be reverted. - // If an irreversible change to the execution state is made, our - // only recourse is to have the tampered thread terminated by - // returning from this function while the goroutine remains - // wired to the thread. The Go runtime will terminate the thread - // and replace it with a fresh one as needed. - - if isReversible { - defer func() { - if isReversible { - // All execution state has been restored without error. - // The thread is once again fungible. - runtime.UnlockOSThread() - } - }() - tid := unix.Gettid() - for f, ns := range reversibleSetnsFlags { - if flags&f != f { - continue - } - // The /proc/thread-self directory was added in Linux 3.17. - // We are not using it to maximize compatibility. - pth := fmt.Sprintf("/proc/self/task/%d/ns/%s", tid, ns) - fd, err := unix.Open(pth, unix.O_RDONLY|unix.O_CLOEXEC, 0) - if err != nil { - started <- &os.PathError{Op: "open", Path: pth, Err: err} - return - } - defer func() { - if isReversible { - if err := unix.Setns(fd, 0); err != nil { - isReversible = false - } - } - _ = unix.Close(fd) - }() - } - } - - // Threads are implemented under Linux as processes which share - // a virtual memory space. Therefore in a multithreaded process - // unshare(2) disassociates parts of the calling thread's - // context from the thread it was clone(2)'d from. - if err := unix.Unshare(flags); err != nil { - started <- os.NewSyscallError("unshare", err) - return - } - - if setupfn != nil { - if err := setupfn(); err != nil { - started <- err - return - } - } - close(started) - - if fn != nil { - fn() - } - }() - - return <-started -} diff --git a/vendor/github.com/moby/go-archive/path.go b/vendor/github.com/moby/go-archive/path.go deleted file mode 100644 index 888a697581d2..000000000000 --- a/vendor/github.com/moby/go-archive/path.go +++ /dev/null @@ -1,20 +0,0 @@ -package archive - -// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter, -// is the system drive. -// On Linux: this is a no-op. -// On Windows: this does the following> -// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path. -// This is used, for example, when validating a user provided path in docker cp. -// If a drive letter is supplied, it must be the system drive. The drive letter -// is always removed. Also, it translates it to OS semantics (IOW / to \). We -// need the path in this syntax so that it can ultimately be concatenated with -// a Windows long-path which doesn't support drive-letters. Examples: -// C: --> Fail -// C:\ --> \ -// a --> a -// /a --> \a -// d:\ --> Fail -func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return checkSystemDriveAndRemoveDriveLetter(path) -} diff --git a/vendor/github.com/moby/go-archive/path_unix.go b/vendor/github.com/moby/go-archive/path_unix.go deleted file mode 100644 index 390264bf8580..000000000000 --- a/vendor/github.com/moby/go-archive/path_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package archive - -// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return path, nil -} diff --git a/vendor/github.com/moby/go-archive/path_windows.go b/vendor/github.com/moby/go-archive/path_windows.go deleted file mode 100644 index 7e18c8e4493d..000000000000 --- a/vendor/github.com/moby/go-archive/path_windows.go +++ /dev/null @@ -1,22 +0,0 @@ -package archive - -import ( - "fmt" - "path/filepath" - "strings" -) - -// checkSystemDriveAndRemoveDriveLetter is the Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - if len(path) == 2 && string(path[1]) == ":" { - return "", fmt.Errorf("no relative path specified in %q", path) - } - if !filepath.IsAbs(path) || len(path) < 2 { - return filepath.FromSlash(path), nil - } - if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { - return "", fmt.Errorf("the specified path is not on the system drive (C:)") - } - return filepath.FromSlash(path[2:]), nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader.go b/vendor/github.com/moby/go-archive/tarheader/tarheader.go deleted file mode 100644 index 03732a4f844b..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader.go +++ /dev/null @@ -1,67 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// assert that we implement [tar.FileInfoNames]. -var _ tar.FileInfoNames = (*nosysFileInfo)(nil) - -// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to -// prevent tar.FileInfoHeader from introspecting it and potentially calling into -// glibc. -// -// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader] -// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102 -type nosysFileInfo struct { - os.FileInfo -} - -// Uname stubs out looking up username. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Uname() (string, error) { - return "", nil -} - -// Gname stubs out looking up group-name. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Gname() (string, error) { - return "", nil -} - -func (fi nosysFileInfo) Sys() interface{} { - // A Sys value of type *tar.Header is safe as it is system-independent. - // The tar.FileInfoHeader function copies the fields into the returned - // header without performing any OS lookups. - if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok { - return sys - } - return nil -} - -// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi. -// -// Compared to the archive/tar.FileInfoHeader function, this function is safe to -// call from a chrooted process as it does not populate fields which would -// require operating system lookups. It behaves identically to -// tar.FileInfoHeader when fi is a FileInfo value returned from -// tar.Header.FileInfo(). -// -// When fi is a FileInfo for a native file, such as returned from os.Stat() and -// os.Lstat(), the returned Header value differs from one returned from -// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not -// set as OS lookups would be required to populate them. The AccessTime and -// ChangeTime fields are not currently set (not yet implemented) although that -// is subject to change. Callers which require the AccessTime or ChangeTime -// fields to be zeroed should explicitly zero them out in the returned Header -// value to avoid any compatibility issues in the future. -func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) - if err != nil { - return nil, err - } - return hdr, sysStat(fi, hdr) -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go deleted file mode 100644 index 9c3311c63abc..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !windows - -package tarheader - -import ( - "archive/tar" - "os" - "runtime" - "syscall" - - "golang.org/x/sys/unix" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. -func sysStat(fi os.FileInfo, hdr *tar.Header) error { - // Devmajor and Devminor are only needed for special devices. - - // In FreeBSD, RDev for regular files is -1 (unless overridden by FS): - // https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531 - // (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241). - - // ZFS in particular does not override the default: - // https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027 - - // Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1). - // Such large values cannot be encoded in a tar header. - if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar { - return nil - } - s, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - return nil - } - - 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 - } - - return nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go deleted file mode 100644 index 5d4483ce2618..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. It is a no-op on Windows. -func sysStat(os.FileInfo, *tar.Header) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/time.go b/vendor/github.com/moby/go-archive/time.go deleted file mode 100644 index 4e9ae9508432..000000000000 --- a/vendor/github.com/moby/go-archive/time.go +++ /dev/null @@ -1,38 +0,0 @@ -package archive - -import ( - "syscall" - "time" - "unsafe" -) - -var ( - minTime = time.Unix(0, 0) - maxTime time.Time -) - -func init() { - if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 { - // This is a 64 bit timespec - // os.Chtimes limits time to the following - maxTime = time.Unix(0, 1<<63-1) - } else { - // This is a 32 bit timespec - maxTime = time.Unix(1<<31-1, 0) - } -} - -func boundTime(t time.Time) time.Time { - if t.Before(minTime) || t.After(maxTime) { - return minTime - } - - return t -} - -func latestTime(t1, t2 time.Time) time.Time { - if t1.Before(t2) { - return t2 - } - return t1 -} diff --git a/vendor/github.com/moby/go-archive/time_nonwindows.go b/vendor/github.com/moby/go-archive/time_nonwindows.go deleted file mode 100644 index 5bfdfa2f17e8..000000000000 --- a/vendor/github.com/moby/go-archive/time_nonwindows.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build !windows - -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/unix" -) - -// chtimes changes the access time and modified time of a file at the given path. -// If the modified time is prior to the Unix Epoch (unixMinTime), or after the -// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this -// case, Chtimes defaults to Unix Epoch, just in case. -func chtimes(name string, atime time.Time, mtime time.Time) error { - return os.Chtimes(name, atime, mtime) -} - -func timeToTimespec(time time.Time) unix.Timespec { - if time.IsZero() { - // Return UTIME_OMIT special value - return unix.Timespec{ - Sec: 0, - Nsec: (1 << 30) - 2, - } - } - return unix.NsecToTimespec(time.UnixNano()) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - utimes := [2]unix.Timespec{ - timeToTimespec(atime), - timeToTimespec(mtime), - } - err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW) - if err != nil && err != unix.ENOSYS { - return err - } - return err -} diff --git a/vendor/github.com/moby/go-archive/time_windows.go b/vendor/github.com/moby/go-archive/time_windows.go deleted file mode 100644 index af1f7c8f3a07..000000000000 --- a/vendor/github.com/moby/go-archive/time_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/windows" -) - -func chtimes(name string, atime time.Time, mtime time.Time) error { - if err := os.Chtimes(name, atime, mtime); err != nil { - return err - } - - pathp, err := windows.UTF16PtrFromString(name) - if err != nil { - return err - } - h, err := windows.CreateFile(pathp, - windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil, - windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0) - if err != nil { - return err - } - defer windows.Close(h) - c := windows.NsecToFiletime(mtime.UnixNano()) - return windows.SetFileTime(h, &c, nil, nil) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/whiteouts.go b/vendor/github.com/moby/go-archive/whiteouts.go deleted file mode 100644 index d20478a10dc1..000000000000 --- a/vendor/github.com/moby/go-archive/whiteouts.go +++ /dev/null @@ -1,23 +0,0 @@ -package archive - -// Whiteouts are files with a special meaning for the layered filesystem. -// Docker uses AUFS whiteout files inside exported archives. In other -// filesystems these files are generated/handled on tar creation/extraction. - -// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a -// filename this means that file has been removed from the base layer. -const WhiteoutPrefix = ".wh." - -// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not -// for removing an actual file. Normally these files are excluded from exported -// archives. -const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix - -// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other -// layers. Normally these should not go into exported archives and all changed -// hardlinks should be copied to the top layer. -const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" - -// WhiteoutOpaqueDir file means directory has been made opaque - meaning -// readdir calls to this directory do not follow to lower layers. -const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" diff --git a/vendor/github.com/moby/go-archive/wrap.go b/vendor/github.com/moby/go-archive/wrap.go deleted file mode 100644 index f8a97254eede..000000000000 --- a/vendor/github.com/moby/go-archive/wrap.go +++ /dev/null @@ -1,59 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "io" -) - -// Generate generates a new archive from the content provided -// as input. -// -// `files` is a sequence of path/content pairs. A new file is -// added to the archive for each pair. -// If the last pair is incomplete, the file is created with an -// empty content. For example: -// -// Generate("foo.txt", "hello world", "emptyfile") -// -// The above call will return an archive with 2 files: -// - ./foo.txt with content "hello world" -// - ./empty with empty content -// -// FIXME: stream content instead of buffering -// FIXME: specify permissions and other archive metadata -func Generate(input ...string) (io.Reader, error) { - files := parseStringPairs(input...) - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - for _, file := range files { - name, content := file[0], file[1] - hdr := &tar.Header{ - Name: name, - Size: int64(len(content)), - } - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(content)); err != nil { - return nil, err - } - } - if err := tw.Close(); err != nil { - return nil, err - } - return buf, nil -} - -func parseStringPairs(input ...string) [][2]string { - output := make([][2]string, 0, len(input)/2+1) - for i := 0; i < len(input); i += 2 { - var pair [2]string - pair[0] = input[i] - if i+1 < len(input) { - pair[1] = input[i+1] - } - output = append(output, pair) - } - return output -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported.go b/vendor/github.com/moby/go-archive/xattr_supported.go deleted file mode 100644 index 652a1f0f349d..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported.go +++ /dev/null @@ -1,52 +0,0 @@ -//go:build linux || darwin || freebsd || netbsd - -package archive - -import ( - "errors" - "fmt" - "io/fs" - - "golang.org/x/sys/unix" -) - -// 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) { - // Start with a 128 length byte array - dest := make([]byte, 128) - sz, err := unix.Lgetxattr(path, attr, dest) - - for errors.Is(err, unix.ERANGE) { - // Buffer too small, use zero-sized buffer to get the actual size - sz, err = unix.Lgetxattr(path, attr, []byte{}) - if err != nil { - return nil, wrapPathError("lgetxattr", path, attr, err) - } - dest = make([]byte, sz) - sz, err = unix.Lgetxattr(path, attr, dest) - } - - if err != nil { - if errors.Is(err, noattr) { - return nil, nil - } - return nil, wrapPathError("lgetxattr", path, attr, err) - } - - return dest[:sz], nil -} - -// 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 wrapPathError(op, path, 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)} -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported_linux.go b/vendor/github.com/moby/go-archive/xattr_supported_linux.go deleted file mode 100644 index f2e76465ae56..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_linux.go +++ /dev/null @@ -1,5 +0,0 @@ -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENODATA diff --git a/vendor/github.com/moby/go-archive/xattr_supported_unix.go b/vendor/github.com/moby/go-archive/xattr_supported_unix.go deleted file mode 100644 index 58a03e4a6371..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_unix.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build darwin || freebsd || netbsd - -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENOATTR diff --git a/vendor/github.com/moby/go-archive/xattr_unsupported.go b/vendor/github.com/moby/go-archive/xattr_unsupported.go deleted file mode 100644 index b0d9165cd94f..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_unsupported.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !netbsd - -package archive - -func lgetxattr(path string, attr string) ([]byte, error) { - return nil, nil -} - -func lsetxattr(path string, attr string, data []byte, flags int) error { - return nil -} diff --git a/vendor/github.com/moby/sys/mount/LICENSE b/vendor/github.com/moby/sys/mount/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/sys/mount/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/moby/sys/mount/doc.go b/vendor/github.com/moby/sys/mount/doc.go deleted file mode 100644 index 86c2e01bbd8c..000000000000 --- a/vendor/github.com/moby/sys/mount/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package mount provides a set of functions to mount and unmount mounts. -// -// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD. -package mount diff --git a/vendor/github.com/moby/sys/mount/flags_bsd.go b/vendor/github.com/moby/sys/mount/flags_bsd.go deleted file mode 100644 index e90f929decc1..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_bsd.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build darwin || freebsd || openbsd - -package mount - -import "golang.org/x/sys/unix" - -const ( - // RDONLY will mount the filesystem as read-only. - RDONLY = unix.MNT_RDONLY - - // NOSUID will not allow set-user-identifier or set-group-identifier bits to - // take effect. - NOSUID = unix.MNT_NOSUID - - // NOEXEC will not allow execution of any binaries on the mounted file system. - NOEXEC = unix.MNT_NOEXEC - - // SYNCHRONOUS will allow any I/O to the file system to be done synchronously. - SYNCHRONOUS = unix.MNT_SYNCHRONOUS - - // NOATIME will not update the file access time when reading from a file. - NOATIME = unix.MNT_NOATIME -) - -// These flags are unsupported. -const ( - BIND = 0 - DIRSYNC = 0 - MANDLOCK = 0 - NODEV = 0 - NODIRATIME = 0 - UNBINDABLE = 0 - RUNBINDABLE = 0 - PRIVATE = 0 - RPRIVATE = 0 - SHARED = 0 - RSHARED = 0 - SLAVE = 0 - RSLAVE = 0 - RBIND = 0 - RELATIME = 0 - REMOUNT = 0 - STRICTATIME = 0 - mntDetach = 0 -) diff --git a/vendor/github.com/moby/sys/mount/flags_linux.go b/vendor/github.com/moby/sys/mount/flags_linux.go deleted file mode 100644 index 0425d0dd633c..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_linux.go +++ /dev/null @@ -1,87 +0,0 @@ -package mount - -import ( - "golang.org/x/sys/unix" -) - -const ( - // RDONLY will mount the file system read-only. - RDONLY = unix.MS_RDONLY - - // NOSUID will not allow set-user-identifier or set-group-identifier bits to - // take effect. - NOSUID = unix.MS_NOSUID - - // NODEV will not interpret character or block special devices on the file - // system. - NODEV = unix.MS_NODEV - - // NOEXEC will not allow execution of any binaries on the mounted file system. - NOEXEC = unix.MS_NOEXEC - - // SYNCHRONOUS will allow I/O to the file system to be done synchronously. - SYNCHRONOUS = unix.MS_SYNCHRONOUS - - // DIRSYNC will force all directory updates within the file system to be done - // synchronously. This affects the following system calls: create, link, - // unlink, symlink, mkdir, rmdir, mknod and rename. - DIRSYNC = unix.MS_DIRSYNC - - // REMOUNT will attempt to remount an already-mounted file system. This is - // commonly used to change the mount flags for a file system, especially to - // make a readonly file system writeable. It does not change device or mount - // point. - REMOUNT = unix.MS_REMOUNT - - // MANDLOCK will force mandatory locks on a filesystem. - MANDLOCK = unix.MS_MANDLOCK - - // NOATIME will not update the file access time when reading from a file. - NOATIME = unix.MS_NOATIME - - // NODIRATIME will not update the directory access time. - NODIRATIME = unix.MS_NODIRATIME - - // BIND remounts a subtree somewhere else. - BIND = unix.MS_BIND - - // RBIND remounts a subtree and all possible submounts somewhere else. - RBIND = unix.MS_BIND | unix.MS_REC - - // UNBINDABLE creates a mount which cannot be cloned through a bind operation. - UNBINDABLE = unix.MS_UNBINDABLE - - // RUNBINDABLE marks the entire mount tree as UNBINDABLE. - RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC - - // PRIVATE creates a mount which carries no propagation abilities. - PRIVATE = unix.MS_PRIVATE - - // RPRIVATE marks the entire mount tree as PRIVATE. - RPRIVATE = unix.MS_PRIVATE | unix.MS_REC - - // SLAVE creates a mount which receives propagation from its master, but not - // vice versa. - SLAVE = unix.MS_SLAVE - - // RSLAVE marks the entire mount tree as SLAVE. - RSLAVE = unix.MS_SLAVE | unix.MS_REC - - // SHARED creates a mount which provides the ability to create mirrors of - // that mount such that mounts and unmounts within any of the mirrors - // propagate to the other mirrors. - SHARED = unix.MS_SHARED - - // RSHARED marks the entire mount tree as SHARED. - RSHARED = unix.MS_SHARED | unix.MS_REC - - // RELATIME updates inode access times relative to modify or change time. - RELATIME = unix.MS_RELATIME - - // STRICTATIME allows to explicitly request full atime updates. This makes - // it possible for the kernel to default to relatime or noatime but still - // allow userspace to override it. - STRICTATIME = unix.MS_STRICTATIME - - mntDetach = unix.MNT_DETACH -) diff --git a/vendor/github.com/moby/sys/mount/flags_unix.go b/vendor/github.com/moby/sys/mount/flags_unix.go deleted file mode 100644 index cf547a0fefc5..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_unix.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build !windows - -package mount - -import ( - "fmt" - "strings" -) - -var flags = map[string]struct { - clear bool - flag int -}{ - "defaults": {false, 0}, - "ro": {false, RDONLY}, - "rw": {true, RDONLY}, - "suid": {true, NOSUID}, - "nosuid": {false, NOSUID}, - "dev": {true, NODEV}, - "nodev": {false, NODEV}, - "exec": {true, NOEXEC}, - "noexec": {false, NOEXEC}, - "sync": {false, SYNCHRONOUS}, - "async": {true, SYNCHRONOUS}, - "dirsync": {false, DIRSYNC}, - "remount": {false, REMOUNT}, - "mand": {false, MANDLOCK}, - "nomand": {true, MANDLOCK}, - "atime": {true, NOATIME}, - "noatime": {false, NOATIME}, - "diratime": {true, NODIRATIME}, - "nodiratime": {false, NODIRATIME}, - "bind": {false, BIND}, - "rbind": {false, RBIND}, - "unbindable": {false, UNBINDABLE}, - "runbindable": {false, RUNBINDABLE}, - "private": {false, PRIVATE}, - "rprivate": {false, RPRIVATE}, - "shared": {false, SHARED}, - "rshared": {false, RSHARED}, - "slave": {false, SLAVE}, - "rslave": {false, RSLAVE}, - "relatime": {false, RELATIME}, - "norelatime": {true, RELATIME}, - "strictatime": {false, STRICTATIME}, - "nostrictatime": {true, STRICTATIME}, -} - -var validFlags = map[string]bool{ - "": true, - "size": true, - "mode": true, - "uid": true, - "gid": true, - "nr_inodes": true, - "nr_blocks": true, - "mpol": true, -} - -var propagationFlags = map[string]bool{ - "bind": true, - "rbind": true, - "unbindable": true, - "runbindable": true, - "private": true, - "rprivate": true, - "shared": true, - "rshared": true, - "slave": true, - "rslave": true, -} - -// MergeTmpfsOptions merge mount options to make sure there is no duplicate. -func MergeTmpfsOptions(options []string) ([]string, error) { - // We use collisions maps to remove duplicates. - // For flag, the key is the flag value (the key for propagation flag is -1) - // For data=value, the key is the data - flagCollisions := map[int]bool{} - dataCollisions := map[string]bool{} - - var newOptions []string - // We process in reverse order - for i := len(options) - 1; i >= 0; i-- { - option := options[i] - if option == "defaults" { - continue - } - if f, ok := flags[option]; ok && f.flag != 0 { - // There is only one propagation mode - key := f.flag - if propagationFlags[option] { - key = -1 - } - // Check to see if there is collision for flag - if !flagCollisions[key] { - // We prepend the option and add to collision map - newOptions = append([]string{option}, newOptions...) - flagCollisions[key] = true - } - continue - } - opt, _, ok := strings.Cut(option, "=") - if !ok || !validFlags[opt] { - return nil, fmt.Errorf("invalid tmpfs option %q", opt) - } - if !dataCollisions[opt] { - // We prepend the option and add to collision map - newOptions = append([]string{option}, newOptions...) - dataCollisions[opt] = true - } - } - - return newOptions, nil -} - -// Parse fstab type mount options into mount() flags -// and device specific data -func parseOptions(options string) (int, string) { - var ( - flag int - data []string - ) - - for _, o := range strings.Split(options, ",") { - // If the option does not exist in the flags table or the flag - // is not supported on the platform, - // then it is a data value for a specific fs type - if f, exists := flags[o]; exists && f.flag != 0 { - if f.clear { - flag &= ^f.flag - } else { - flag |= f.flag - } - } else { - data = append(data, o) - } - } - return flag, strings.Join(data, ",") -} diff --git a/vendor/github.com/moby/sys/mount/mount_errors.go b/vendor/github.com/moby/sys/mount/mount_errors.go deleted file mode 100644 index c08bc12cda32..000000000000 --- a/vendor/github.com/moby/sys/mount/mount_errors.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !windows - -package mount - -import "strconv" - -// mountError records an error from mount or unmount operation -type mountError struct { - op string - source, target string - flags uintptr - data string - err error -} - -func (e *mountError) Error() string { - out := e.op + " " - - if e.source != "" { - out += e.source + ":" + e.target - } else { - out += e.target - } - - if e.flags != uintptr(0) { - out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16) - } - if e.data != "" { - out += ", data: " + e.data - } - - out += ": " + e.err.Error() - return out -} - -// Cause returns the underlying cause of the error. -// This is a convention used in github.com/pkg/errors -func (e *mountError) Cause() error { - return e.err -} - -// Unwrap returns the underlying error. -// This is a convention used in golang 1.13+ -func (e *mountError) Unwrap() error { - return e.err -} diff --git a/vendor/github.com/moby/sys/mount/mount_unix.go b/vendor/github.com/moby/sys/mount/mount_unix.go deleted file mode 100644 index 4abd4ca0ccf8..000000000000 --- a/vendor/github.com/moby/sys/mount/mount_unix.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build !windows - -package mount - -import ( - "fmt" - "sort" - - "github.com/moby/sys/mountinfo" - "golang.org/x/sys/unix" -) - -// Mount will mount filesystem according to the specified configuration. -// Options must be specified like the mount or fstab unix commands: -// "opt1=val1,opt2=val2". See flags.go for supported option flags. -func Mount(device, target, mType, options string) error { - flag, data := parseOptions(options) - return mount(device, target, mType, uintptr(flag), data) -} - -// Unmount lazily unmounts a filesystem on supported platforms, otherwise does -// a normal unmount. If target is not a mount point, no error is returned. -func Unmount(target string) error { - err := unix.Unmount(target, mntDetach) - if err == nil || err == unix.EINVAL { - // Ignore "not mounted" error here. Note the same error - // can be returned if flags are invalid, so this code - // assumes that the flags value is always correct. - return nil - } - - return &mountError{ - op: "umount", - target: target, - flags: uintptr(mntDetach), - err: err, - } -} - -// RecursiveUnmount unmounts the target and all mounts underneath, starting -// with the deepest mount first. The argument does not have to be a mount -// point itself. -func RecursiveUnmount(target string) error { - // Fast path, works if target is a mount point that can be unmounted. - // On Linux, mntDetach flag ensures a recursive unmount. For other - // platforms, if there are submounts, we'll get EBUSY (and fall back - // to the slow path). NOTE we do not ignore EINVAL here as target might - // not be a mount point itself (but there can be mounts underneath). - if err := unix.Unmount(target, mntDetach); err == nil { - return nil - } - - // Slow path: get all submounts, sort, unmount one by one. - mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target)) - if err != nil { - return err - } - - // Make the deepest mount be first - sort.Slice(mounts, func(i, j int) bool { - return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint) - }) - - var ( - suberr error - lastMount = len(mounts) - 1 - ) - for i, m := range mounts { - err = Unmount(m.Mountpoint) - if err != nil { - if i == lastMount { - if suberr != nil { - // TODO: switch to %w for suberr once we stop supporting go < 1.20. - return fmt.Errorf("%w (possible cause: %s)", err, suberr.Error()) - } - return err - } - // This is a submount, we can ignore the error for now, - // the final unmount will fail if this is a real problem. - // With that in mind, the _first_ failed unmount error - // might be the real error cause, so let's keep it. - if suberr == nil { - suberr = err - } - } - } - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_freebsd.go b/vendor/github.com/moby/sys/mount/mounter_freebsd.go deleted file mode 100644 index 14485069fa0e..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_freebsd.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build freebsd && cgo - -package mount - -/* -#include -#include -#include -#include -#include -#include -*/ -import "C" - -import ( - "strings" - "syscall" - "unsafe" -) - -func allocateIOVecs(options []string) []C.struct_iovec { - out := make([]C.struct_iovec, len(options)) - for i, option := range options { - out[i].iov_base = unsafe.Pointer(C.CString(option)) - out[i].iov_len = C.size_t(len(option) + 1) - } - return out -} - -func mount(device, target, mType string, flag uintptr, data string) error { - isNullFS := false - - xs := strings.Split(data, ",") - for _, x := range xs { - if x == "bind" { - isNullFS = true - } - } - - options := []string{"fspath", target} - if isNullFS { - options = append(options, "fstype", "nullfs", "target", device) - } else { - options = append(options, "fstype", mType, "from", device) - } - rawOptions := allocateIOVecs(options) - for _, rawOption := range rawOptions { - defer C.free(rawOption.iov_base) - } - - if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: syscall.Errno(errno), - } - } - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_linux.go b/vendor/github.com/moby/sys/mount/mounter_linux.go deleted file mode 100644 index 4e18f4b6786c..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_linux.go +++ /dev/null @@ -1,72 +0,0 @@ -package mount - -import ( - "golang.org/x/sys/unix" -) - -const ( - // ptypes is the set propagation types. - ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE - - // pflags is the full set valid flags for a change propagation call. - pflags = ptypes | unix.MS_REC | unix.MS_SILENT - - // broflags is the combination of bind and read only - broflags = unix.MS_BIND | unix.MS_RDONLY -) - -// isremount returns true if either device name or flags identify a remount request, false otherwise. -func isremount(device string, flags uintptr) bool { - switch { - // We treat device "" and "none" as a remount request to provide compatibility with - // requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts. - case flags&unix.MS_REMOUNT != 0, device == "", device == "none": - return true - default: - return false - } -} - -func mount(device, target, mType string, flags uintptr, data string) error { - oflags := flags &^ ptypes - if !isremount(device, flags) || data != "" { - // Initial call applying all non-propagation flags for mount - // or remount with changed data - if err := unix.Mount(device, target, mType, oflags, data); err != nil { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: oflags, - data: data, - err: err, - } - } - } - - if flags&ptypes != 0 { - // Change the propagation type. - if err := unix.Mount("", target, "", flags&pflags, ""); err != nil { - return &mountError{ - op: "remount", - target: target, - flags: flags & pflags, - err: err, - } - } - } - - if oflags&broflags == broflags { - // Remount the bind to apply read only. - if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil { - return &mountError{ - op: "remount-ro", - target: target, - flags: oflags | unix.MS_REMOUNT, - err: err, - } - } - } - - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_openbsd.go b/vendor/github.com/moby/sys/mount/mounter_openbsd.go deleted file mode 100644 index 86a0b242a797..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_openbsd.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build openbsd && cgo - -/* - Due to how OpenBSD mount(2) works, filesystem types need to be - supported explicitly since it uses separate structs to pass - filesystem-specific arguments. - - For now only UFS/FFS is supported as it's the default fs - on OpenBSD systems. - - See: https://man.openbsd.org/mount.2 -*/ - -package mount - -/* -#include -#include -*/ -import "C" - -import ( - "fmt" - "syscall" - "unsafe" -) - -func createExportInfo(readOnly bool) C.struct_export_args { - exportFlags := C.int(0) - if readOnly { - exportFlags = C.MNT_EXRDONLY - } - out := C.struct_export_args{ - ex_root: 0, - ex_flags: exportFlags, - } - return out -} - -func createUfsArgs(device string, readOnly bool) unsafe.Pointer { - out := &C.struct_ufs_args{ - fspec: C.CString(device), - export_info: createExportInfo(readOnly), - } - return unsafe.Pointer(out) -} - -func mount(device, target, mType string, flag uintptr, data string) error { - readOnly := flag&RDONLY != 0 - - var fsArgs unsafe.Pointer - - switch mType { - case "ffs": - fsArgs = createUfsArgs(device, readOnly) - default: - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: fmt.Errorf("unsupported file system type: %s", mType), - } - } - - if errno := C.mount(C.CString(mType), C.CString(target), C.int(flag), fsArgs); errno != 0 { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: syscall.Errno(errno), - } - } - - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_unsupported.go b/vendor/github.com/moby/sys/mount/mounter_unsupported.go deleted file mode 100644 index 17b8b650ec80..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build (!linux && !freebsd && !openbsd && !windows) || (freebsd && !cgo) || (openbsd && !cgo) - -package mount - -func mount(device, target, mType string, flag uintptr, data string) error { - panic("cgo required on freebsd and openbsd") -} diff --git a/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go b/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go deleted file mode 100644 index 948e6bacd8ae..000000000000 --- a/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go +++ /dev/null @@ -1,73 +0,0 @@ -package mount - -import "github.com/moby/sys/mountinfo" - -// MakeShared ensures a mounted filesystem has the SHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeShared(mountPoint string) error { - return ensureMountedAs(mountPoint, SHARED) -} - -// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRShared(mountPoint string) error { - return ensureMountedAs(mountPoint, RSHARED) -} - -// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled. -// See the supported options in flags.go for further reference. -func MakePrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, PRIVATE) -} - -// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeRPrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, RPRIVATE) -} - -// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, SLAVE) -} - -// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, RSLAVE) -} - -// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, UNBINDABLE) -} - -// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount -// option enabled. See the supported options in flags.go for further reference. -func MakeRUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, RUNBINDABLE) -} - -// MakeMount ensures that the file or directory given is a mount point, -// bind mounting it to itself it case it is not. -func MakeMount(mnt string) error { - mounted, err := mountinfo.Mounted(mnt) - if err != nil { - return err - } - if mounted { - return nil - } - - return mount(mnt, mnt, "none", uintptr(BIND), "") -} - -func ensureMountedAs(mnt string, flags int) error { - if err := MakeMount(mnt); err != nil { - return err - } - - return mount("", mnt, "none", uintptr(flags), "") -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 199b09c2959f..3927daaa99ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -795,14 +795,6 @@ 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 -github.com/moby/go-archive/chrootarchive -github.com/moby/go-archive/compression -github.com/moby/go-archive/internal/mounttree -github.com/moby/go-archive/internal/unshare -github.com/moby/go-archive/tarheader # github.com/moby/locker v1.0.1 ## explicit; go 1.13 github.com/moby/locker @@ -823,9 +815,6 @@ github.com/moby/profiles/seccomp # github.com/moby/sys/capability v0.4.0 ## explicit; go 1.21 github.com/moby/sys/capability -# github.com/moby/sys/mount v0.3.5 -## explicit; go 1.18 -github.com/moby/sys/mount # github.com/moby/sys/mountinfo v0.7.2 ## explicit; go 1.17 github.com/moby/sys/mountinfo From 0bad7f46d16f168553c9ab6ca9bfe7f443d1dfcb Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:41:17 +0200 Subject: [PATCH 2/2] file: constrain ADD archive extraction with os.Root Replace direct containerd archive application in the Dockerfile ADD unpack path with a local tar extractor that opens the destination through os.Root. This keeps extraction operations scoped to the destination root while preserving regular files, directories, symlinks, hardlinks, whiteout files, ownership mapping, and Windows no-owner behavior. Add coverage for parent-directory paths, Windows volume paths, hardlinks outside the destination, and symlink traversal through both archive-created and preexisting symlinks. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- solver/llbsolver/file/unpack.go | 33 +- solver/llbsolver/file/unpack_root.go | 314 +++++++++++++++++++ solver/llbsolver/file/unpack_test.go | 93 +++++- solver/llbsolver/file/unpack_unix.go | 16 +- solver/llbsolver/file/unpack_unix_test.go | 86 +++-- solver/llbsolver/file/unpack_windows.go | 15 +- solver/llbsolver/file/unpack_windows_test.go | 16 + 7 files changed, 484 insertions(+), 89 deletions(-) create mode 100644 solver/llbsolver/file/unpack_root.go diff --git a/solver/llbsolver/file/unpack.go b/solver/llbsolver/file/unpack.go index 2fc536a8a28e..202580b5b109 100644 --- a/solver/llbsolver/file/unpack.go +++ b/solver/llbsolver/file/unpack.go @@ -6,7 +6,6 @@ import ( "os" "time" - "github.com/containerd/containerd/v2/pkg/archive" "github.com/containerd/continuity/fs" "github.com/moby/buildkit/util/archiveutil" "github.com/moby/sys/user" @@ -42,40 +41,10 @@ func unpack(ctx context.Context, srcRoot string, src string, destRoot string, de } defer rdr.Close() - opts := []archive.ApplyOpt{ - // Disable containerd's image-layer whiteout conversion for Dockerfile ADD - // archives, where .wh.* entries should be extracted as files. - archive.WithConvertWhiteout(func(_ *tar.Header, _ string) (bool, error) { - return true, nil - }), - archive.WithFilter(ownerMapper(u, idmap)), - } - opts = append(opts, unpackPlatformApplyOpts()...) - - _, err = archive.Apply(ctx, dest, rdr, opts...) + err = applyRootArchive(ctx, dest, rdr, u, idmap, unpackNoSameOwner()) return true, err } -func ownerMapper(u *copy.User, idmap *user.IdentityMapping) archive.Filter { - return func(hdr *tar.Header) (bool, error) { - uid, gid := hdr.Uid, hdr.Gid - // Match go-archive behavior: remap archive header IDs first, then let - // explicit --chown values override the header ownership. - if idmap != nil { - var err error - uid, gid, err = idmap.ToHost(uid, gid) - if err != nil { - return false, err - } - } - if u != nil { - uid, gid = u.UID, u.GID - } - hdr.Uid, hdr.Gid = uid, gid - return true, nil - } -} - func isArchivePath(path string) bool { fi, err := os.Lstat(path) if err != nil { diff --git a/solver/llbsolver/file/unpack_root.go b/solver/llbsolver/file/unpack_root.go new file mode 100644 index 000000000000..def4024c4c9a --- /dev/null +++ b/solver/llbsolver/file/unpack_root.go @@ -0,0 +1,314 @@ +package file + +import ( + "archive/tar" + "context" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/moby/sys/user" + "github.com/pkg/errors" + copy "github.com/tonistiigi/fsutil/copy" +) + +type rootDirTime struct { + name string + atime time.Time + mtime time.Time +} + +func applyRootArchive(ctx context.Context, dest string, r io.Reader, u *copy.User, idmap *user.IdentityMapping, noSameOwner bool) error { + root, err := os.OpenRoot(dest) + if err != nil { + return err + } + defer root.Close() + + tr := tar.NewReader(r) + var dirs []rootDirTime + var copyBuf []byte + for { + select { + case <-ctx.Done(): + return context.Cause(ctx) + default: + } + + hdr, err := tr.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader { + continue + } + + name, err := cleanRootTarPath(hdr.Name) + if err != nil { + return err + } + if name == "" { + continue + } + hdr.Name = name + + if err := mapArchiveHeaderOwner(hdr, u, idmap); err != nil { + return err + } + + opName, err := resolveRootPath(root, name, hdr.Typeflag == tar.TypeDir) + if err != nil { + return err + } + if opName == "" { + continue + } + + parent := filepath.Dir(opName) + if parent != "." { + if err := root.MkdirAll(parent, 0o755); err != nil { + return err + } + } + + fi, err := root.Lstat(opName) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + } else if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { + if err := root.RemoveAll(opName); err != nil { + return err + } + } + + mode := hdr.FileInfo().Mode() + switch hdr.Typeflag { + case tar.TypeDir: + if err := root.Mkdir(opName, mode.Perm()); err != nil && !errors.Is(err, os.ErrExist) { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + if err := root.Chmod(opName, mode); err != nil { + return err + } + dirs = append(dirs, rootDirTime{ + name: opName, + atime: headerAccessTime(hdr), + mtime: hdr.ModTime, + }) + case tar.TypeReg, 0: + // A zero typeflag is the historic tar regular-file marker. + file, err := root.OpenFile(opName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) + if err != nil { + return err + } + if copyBuf == nil { + copyBuf = make([]byte, 32*1024) + } + for { + select { + case <-ctx.Done(): + file.Close() + return context.Cause(ctx) + default: + } + + nr, er := tr.Read(copyBuf) + if nr > 0 { + nw, ew := file.Write(copyBuf[:nr]) + if ew != nil { + file.Close() + return ew + } + if nw != nr { + file.Close() + return io.ErrShortWrite + } + } + if er == io.EOF { + break + } + if er != nil { + file.Close() + return er + } + } + if !noSameOwner { + if err := file.Chown(hdr.Uid, hdr.Gid); err != nil { + file.Close() + return err + } + } + if err := file.Chmod(mode); err != nil { + file.Close() + return err + } + if err := root.Chtimes(opName, headerAccessTime(hdr), hdr.ModTime); err != nil { + file.Close() + return err + } + if err := file.Close(); err != nil { + return err + } + case tar.TypeSymlink: + // Preserve the archive's link target. os.Root does not validate the + // target here. Later extraction resolves absolute targets relative to + // the extraction root and rejects relative targets that escape it. + if err := root.Symlink(hdr.Linkname, opName); err != nil { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + case tar.TypeLink: + linkName, err := cleanRootTarPath(hdr.Linkname) + if err != nil { + return err + } + if linkName == "" { + return errors.Errorf("archive hardlink %q targets extraction root", hdr.Name) + } + linkName, err = resolveRootPath(root, linkName, false) + if err != nil { + return err + } + if err := root.Link(linkName, opName); err != nil { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + fi, err := root.Lstat(opName) + if err != nil { + return err + } + if fi.Mode()&os.ModeSymlink == 0 { + if err := root.Chmod(opName, mode); err != nil { + return err + } + if err := root.Chtimes(opName, headerAccessTime(hdr), hdr.ModTime); err != nil { + return err + } + } + default: + return errors.Errorf("unsupported tar entry %q type %d", hdr.Name, hdr.Typeflag) + } + } + + for i := len(dirs) - 1; i >= 0; i-- { + if err := root.Chtimes(dirs[i].name, dirs[i].atime, dirs[i].mtime); err != nil { + return err + } + } + return nil +} + +func cleanRootTarPath(name string) (string, error) { + name = filepath.Clean(name) + if filepath.VolumeName(name) != "" { + return "", errors.Errorf("archive path %q is outside extraction root", name) + } + // Match chroot-style extraction: /foo is extracted as foo under the + // destination root, while paths that still escape are rejected below. + name = strings.TrimLeft(name, string(filepath.Separator)) + if name == "." || name == "" { + return "", nil + } + if !filepath.IsLocal(name) { + return "", errors.Errorf("archive path %q is outside extraction root", name) + } + return name, nil +} + +func resolveRootPath(root *os.Root, name string, followLeaf bool) (string, error) { + original := name + for range 255 { + parts := strings.Split(name, string(filepath.Separator)) + resolved := make([]string, 0, len(parts)) + followed := false + for i, part := range parts { + if part == "" || part == "." { + continue + } + candidate := filepath.Join(append(resolved, part)...) + if i == len(parts)-1 && !followLeaf { + resolved = append(resolved, part) + continue + } + fi, err := root.Lstat(candidate) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + resolved = append(resolved, parts[i:]...) + return filepath.Join(resolved...), nil + } + return "", err + } + if fi.Mode()&os.ModeSymlink == 0 { + resolved = append(resolved, part) + continue + } + + target, err := root.Readlink(candidate) + if err != nil { + return "", err + } + if filepath.IsAbs(target) || filepath.VolumeName(target) != "" { + name, err = cleanRootTarPath(target) + if err != nil { + return "", err + } + } else { + name = filepath.Clean(filepath.Join(filepath.Dir(candidate), target)) + if name == "." { + name = "" + } + if name != "" && !filepath.IsLocal(name) { + return "", errors.Errorf("archive symlink %q points outside extraction root", candidate) + } + } + remaining := filepath.Join(parts[i+1:]...) + if remaining != "" { + name = filepath.Join(name, remaining) + } + followed = true + break + } + if !followed { + return filepath.Join(resolved...), nil + } + } + return "", errors.Errorf("too many symlinks resolving archive path %q", original) +} + +func headerAccessTime(hdr *tar.Header) time.Time { + if !hdr.AccessTime.IsZero() { + return hdr.AccessTime + } + return hdr.ModTime +} + +func mapArchiveHeaderOwner(hdr *tar.Header, u *copy.User, idmap *user.IdentityMapping) error { + uid, gid := hdr.Uid, hdr.Gid + // Match go-archive behavior: remap archive header IDs first, then let + // explicit --chown values override the header ownership. + if idmap != nil { + var err error + uid, gid, err = idmap.ToHost(uid, gid) + if err != nil { + return err + } + } + if u != nil { + uid, gid = u.UID, u.GID + } + hdr.Uid, hdr.Gid = uid, gid + return nil +} diff --git a/solver/llbsolver/file/unpack_test.go b/solver/llbsolver/file/unpack_test.go index 4eddefcd60f6..ef60bfa7f87b 100644 --- a/solver/llbsolver/file/unpack_test.go +++ b/solver/llbsolver/file/unpack_test.go @@ -2,6 +2,9 @@ package file import ( "archive/tar" + "bytes" + "os" + "path/filepath" "testing" "github.com/moby/sys/user" @@ -18,9 +21,8 @@ func TestOwnerMapperMapsArchiveHeader(t *testing.T) { } hdr := &tar.Header{Uid: 2, Gid: 3} - ok, err := ownerMapper(nil, idmap)(hdr) + err := mapArchiveHeaderOwner(hdr, nil, idmap) require.NoError(t, err) - require.True(t, ok) require.Equal(t, 1002, hdr.Uid) require.Equal(t, 2003, hdr.Gid) } @@ -34,13 +36,90 @@ func TestOwnerMapperChownOverridesArchiveHeader(t *testing.T) { } hdr := &tar.Header{Uid: 2, Gid: 3} - ok, err := ownerMapper(©.User{UID: 5, GID: 6}, idmap)(hdr) + err := mapArchiveHeaderOwner(hdr, ©.User{UID: 5, GID: 6}, idmap) require.NoError(t, err) - require.True(t, ok) require.Equal(t, 5, hdr.Uid) require.Equal(t, 6, hdr.Gid) } +func TestUnpackPreservesWhiteoutFiles(t *testing.T) { + dest := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "victim", "victim") + writeTarFile(t, tw, ".wh.victim", "whiteout") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(dest, "victim")) + require.NoError(t, err) + require.Equal(t, "victim", string(dt)) + + dt, err = os.ReadFile(filepath.Join(dest, ".wh.victim")) + require.NoError(t, err) + require.Equal(t, "whiteout", string(dt)) +} + +func TestUnpackRejectsParentDirectoryPath(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "../outside/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "archive path escaped destination") +} + +func TestUnpackAbsolutePathIsRelativeToDestination(t *testing.T) { + dest := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "/absolute", "content") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(dest, "absolute")) + require.NoError(t, err) + require.Equal(t, "content", string(dt)) +} + +func TestUnpackDoesNotHardlinkOutsideDestination(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(outside, "target"), []byte("target"), 0o644)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "hardlink", + Typeflag: tar.TypeLink, + Linkname: filepath.ToSlash(filepath.Join(outside, "target")), + Mode: 0o644, + })) + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(outside, "target")) + require.NoError(t, err) + require.Equal(t, "target", string(dt)) +} + func writeTarFile(t *testing.T, tw *tar.Writer, name, content string) { t.Helper() @@ -54,3 +133,9 @@ func writeTarFile(t *testing.T, tw *tar.Writer, name, content string) { _, err = tw.Write([]byte(content)) require.NoError(t, err) } + +func applyArchiveNoSameOwner(t *testing.T, dest string, dt []byte) error { + t.Helper() + + return applyRootArchive(t.Context(), dest, bytes.NewReader(dt), nil, nil, true) +} diff --git a/solver/llbsolver/file/unpack_unix.go b/solver/llbsolver/file/unpack_unix.go index 275e97ec6bdb..2fc52f949737 100644 --- a/solver/llbsolver/file/unpack_unix.go +++ b/solver/llbsolver/file/unpack_unix.go @@ -2,8 +2,18 @@ package file -import "github.com/containerd/containerd/v2/pkg/archive" +import ( + "archive/tar" + "os" +) -func unpackPlatformApplyOpts() []archive.ApplyOpt { - return nil +func unpackNoSameOwner() bool { + return false +} + +func applyRootOwner(root *os.Root, name string, hdr *tar.Header, noSameOwner bool) error { + if noSameOwner { + return nil + } + return root.Lchown(name, hdr.Uid, hdr.Gid) } diff --git a/solver/llbsolver/file/unpack_unix_test.go b/solver/llbsolver/file/unpack_unix_test.go index 3969a081bfa0..8efbc50e102c 100644 --- a/solver/llbsolver/file/unpack_unix_test.go +++ b/solver/llbsolver/file/unpack_unix_test.go @@ -9,40 +9,49 @@ import ( "path/filepath" "testing" - "github.com/containerd/containerd/v2/pkg/archive" "github.com/stretchr/testify/require" ) -func TestUnpackPreservesWhiteoutFiles(t *testing.T) { - if os.Geteuid() != 0 { - t.Skip("archive apply preserves tar ownership and requires root") - } - - srcRoot := t.TempDir() - destRoot := t.TempDir() +func TestUnpackWritesThroughRootLocalAbsoluteSymlink(t *testing.T) { + dest := t.TempDir() + require.NoError(t, os.Mkdir(filepath.Join(dest, "run"), 0o755)) + require.NoError(t, os.Mkdir(filepath.Join(dest, "var"), 0o755)) + require.NoError(t, os.Symlink("/run", filepath.Join(dest, "var", "run"))) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) - writeTarFile(t, tw, "victim", "victim") - writeTarFile(t, tw, ".wh.victim", "whiteout") + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "var/run/act", + Typeflag: tar.TypeDir, + Mode: 0o755, + })) + writeTarFile(t, tw, "var/run/act/file", "content") + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "var/run/act/file.link", + Typeflag: tar.TypeLink, + Linkname: "var/run/act/file", + Mode: 0o644, + })) require.NoError(t, tw.Close()) - require.NoError(t, os.WriteFile(filepath.Join(srcRoot, "archive.tar"), buf.Bytes(), 0o600)) + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) - ok, err := unpack(t.Context(), srcRoot, "archive.tar", destRoot, "/", nil, nil, nil, nil) + dt, err := os.ReadFile(filepath.Join(dest, "run", "act", "file")) require.NoError(t, err) - require.True(t, ok) + require.Equal(t, "content", string(dt)) - dt, err := os.ReadFile(filepath.Join(destRoot, "victim")) + link, err := os.Readlink(filepath.Join(dest, "var", "run")) require.NoError(t, err) - require.Equal(t, "victim", string(dt)) + require.Equal(t, "/run", link) - dt, err = os.ReadFile(filepath.Join(destRoot, ".wh.victim")) + fileInfo, err := os.Stat(filepath.Join(dest, "run", "act", "file")) require.NoError(t, err) - require.Equal(t, "whiteout", string(dt)) + linkInfo, err := os.Stat(filepath.Join(dest, "run", "act", "file.link")) + require.NoError(t, err) + require.True(t, os.SameFile(fileInfo, linkInfo)) } -func TestApplyDoesNotWriteThroughArchiveSymlink(t *testing.T) { +func TestUnpackDoesNotWriteThroughAbsoluteArchiveSymlink(t *testing.T) { parent := t.TempDir() dest := filepath.Join(parent, "dest") outside := filepath.Join(parent, "outside") @@ -66,58 +75,45 @@ func TestApplyDoesNotWriteThroughArchiveSymlink(t *testing.T) { require.True(t, os.IsNotExist(err), "archive-created symlink escaped destination") } -func TestApplyDoesNotWriteThroughPreexistingSymlink(t *testing.T) { +func TestUnpackRejectsRelativeArchiveSymlinkEscape(t *testing.T) { parent := t.TempDir() dest := filepath.Join(parent, "dest") outside := filepath.Join(parent, "outside") require.NoError(t, os.Mkdir(dest, 0o755)) require.NoError(t, os.Mkdir(outside, 0o755)) - require.NoError(t, os.Symlink(outside, filepath.Join(dest, "escape"))) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "escape", + Typeflag: tar.TypeSymlink, + Linkname: "../outside", + Mode: 0o777, + })) writeTarFile(t, tw, "escape/pwned", "pwned") require.NoError(t, tw.Close()) - require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) _, err := os.Stat(filepath.Join(outside, "pwned")) - require.True(t, os.IsNotExist(err), "preexisting symlink escaped destination") + require.True(t, os.IsNotExist(err), "archive-created symlink escaped destination") } -func TestApplyDoesNotHardlinkOutsideDestination(t *testing.T) { +func TestUnpackRejectsRelativePreexistingSymlinkEscape(t *testing.T) { parent := t.TempDir() dest := filepath.Join(parent, "dest") outside := filepath.Join(parent, "outside") require.NoError(t, os.Mkdir(dest, 0o755)) require.NoError(t, os.Mkdir(outside, 0o755)) - require.NoError(t, os.WriteFile(filepath.Join(outside, "target"), []byte("target"), 0o644)) + require.NoError(t, os.Symlink("../outside", filepath.Join(dest, "escape"))) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) - require.NoError(t, tw.WriteHeader(&tar.Header{ - Name: "hardlink", - Typeflag: tar.TypeLink, - Linkname: filepath.ToSlash(filepath.Join(outside, "target")), - Mode: 0o644, - })) + writeTarFile(t, tw, "escape/pwned", "pwned") require.NoError(t, tw.Close()) require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) - dt, err := os.ReadFile(filepath.Join(outside, "target")) - require.NoError(t, err) - require.Equal(t, "target", string(dt)) -} - -func applyArchiveNoSameOwner(t *testing.T, dest string, dt []byte) error { - t.Helper() - - _, err := archive.Apply(t.Context(), dest, bytes.NewReader(dt), - archive.WithConvertWhiteout(func(_ *tar.Header, _ string) (bool, error) { - return true, nil - }), - archive.WithNoSameOwner(), - ) - return err + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "preexisting symlink escaped destination") } diff --git a/solver/llbsolver/file/unpack_windows.go b/solver/llbsolver/file/unpack_windows.go index 03ac67eeba69..b18b9aa1e329 100644 --- a/solver/llbsolver/file/unpack_windows.go +++ b/solver/llbsolver/file/unpack_windows.go @@ -2,10 +2,15 @@ package file -import "github.com/containerd/containerd/v2/pkg/archive" +import ( + "archive/tar" + "os" +) -func unpackPlatformApplyOpts() []archive.ApplyOpt { - // Windows does not support lchown. The old go-archive path skipped ownership - // changes on Windows while still extracting the archive contents. - return []archive.ApplyOpt{archive.WithNoSameOwner()} +func unpackNoSameOwner() bool { + return true +} + +func applyRootOwner(*os.Root, string, *tar.Header, bool) error { + return nil } diff --git a/solver/llbsolver/file/unpack_windows_test.go b/solver/llbsolver/file/unpack_windows_test.go index 68a2365efd69..61eb005753ad 100644 --- a/solver/llbsolver/file/unpack_windows_test.go +++ b/solver/llbsolver/file/unpack_windows_test.go @@ -31,3 +31,19 @@ func TestUnpackSkipsSameOwnerOnWindows(t *testing.T) { require.NoError(t, err) require.Equal(t, "content", string(dt)) } + +func TestUnpackRejectsWindowsVolumePath(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + require.NoError(t, os.Mkdir(dest, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, filepath.VolumeName(parent)+`/pwned`, "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(dest, "pwned")) + require.True(t, os.IsNotExist(err), "volume-qualified archive path was extracted") +}