From 9fbd0e03956bc1d3db9923b09e5ad669f84ffbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 22 Apr 2024 22:20:13 +0200 Subject: [PATCH 1/5] Don't look for the binary digest when pulling layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code path is usually never triggered because the annotations are present; and it was broken until recently. Remove it to simplify the code and analysis. Signed-off-by: Miloslav Trmač --- pkg/chunked/compression_linux.go | 48 +++++------------------------ pkg/chunked/internal/compression.go | 5 +++ pkg/chunked/storage_linux.go | 4 +-- pkg/chunked/zstdchunked_test.go | 2 +- 4 files changed, 16 insertions(+), 43 deletions(-) diff --git a/pkg/chunked/compression_linux.go b/pkg/chunked/compression_linux.go index 38a892a6ef..4196c9ce62 100644 --- a/pkg/chunked/compression_linux.go +++ b/pkg/chunked/compression_linux.go @@ -132,48 +132,16 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, return manifestUncompressed, tocOffset, nil } -// readZstdChunkedManifest reads the zstd:chunked manifest from the seekable stream blobStream. The blob total size must -// be specified. -// This function uses the io.github.containers.zstd-chunked. annotations when specified. -func readZstdChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, tocDigest digest.Digest, annotations map[string]string) ([]byte, []byte, int64, error) { - footerSize := int64(internal.FooterSizeSupported) - if blobSize <= footerSize { - return nil, nil, 0, errors.New("blob too small") +// readZstdChunkedManifest reads the zstd:chunked manifest from the seekable stream blobStream. +func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) ([]byte, []byte, int64, error) { + offsetMetadata := annotations[internal.ManifestInfoKey] + if offsetMetadata == "" { + return nil, nil, 0, fmt.Errorf("%q annotation missing", internal.ManifestInfoKey) } - var footerData internal.ZstdChunkedFooterData - - if offsetMetadata := annotations[internal.ManifestInfoKey]; offsetMetadata != "" { - var err error - footerData, err = internal.ReadFooterDataFromAnnotations(annotations) - if err != nil { - return nil, nil, 0, err - } - } else { - chunk := ImageSourceChunk{ - Offset: uint64(blobSize - footerSize), - Length: uint64(footerSize), - } - parts, errs, err := blobStream.GetBlobAt([]ImageSourceChunk{chunk}) - if err != nil { - return nil, nil, 0, err - } - var reader io.ReadCloser - select { - case r := <-parts: - reader = r - case err := <-errs: - return nil, nil, 0, err - } - footer := make([]byte, footerSize) - if _, err := io.ReadFull(reader, footer); err != nil { - return nil, nil, 0, err - } - - footerData, err = internal.ReadFooterDataFromBlob(footer) - if err != nil { - return nil, nil, 0, err - } + footerData, err := internal.ReadFooterDataFromAnnotations(annotations) + if err != nil { + return nil, nil, 0, err } if footerData.ManifestType != internal.ManifestTypeCRFS { diff --git a/pkg/chunked/internal/compression.go b/pkg/chunked/internal/compression.go index f52a07a9f8..53fb98ae2d 100644 --- a/pkg/chunked/internal/compression.go +++ b/pkg/chunked/internal/compression.go @@ -200,6 +200,11 @@ func ZstdWriterWithLevel(dest io.Writer, level int) (*zstd.Encoder, error) { } // ZstdChunkedFooterData contains all the data stored in the zstd:chunked footer. +// This footer exists to make the blobs self-describing, our implementation +// never reads it: +// Partial pull security hinges on the TOC digest, and that exists as a layer annotation; +// so we are relying on the layer annotations anyway, and doing so means we can avoid +// a round-trip to fetch this binary footer. type ZstdChunkedFooterData struct { ManifestType uint64 diff --git a/pkg/chunked/storage_linux.go b/pkg/chunked/storage_linux.go index 82966b8a58..1c4f38663d 100644 --- a/pkg/chunked/storage_linux.go +++ b/pkg/chunked/storage_linux.go @@ -314,7 +314,7 @@ func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobDige } func makeZstdChunkedDiffer(ctx context.Context, store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, storeOpts *types.StoreOptions) (*chunkedDiffer, error) { - manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(iss, blobSize, tocDigest, annotations) + manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(iss, tocDigest, annotations) if err != nil { return nil, fmt.Errorf("read zstd:chunked manifest: %w", err) } @@ -1701,7 +1701,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff if tocDigest == nil { return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("internal error: just-created zstd:chunked missing TOC digest") } - manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(fileSource, c.blobSize, *tocDigest, annotations) + manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(fileSource, *tocDigest, annotations) if err != nil { return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("read zstd:chunked manifest: %w", err) } diff --git a/pkg/chunked/zstdchunked_test.go b/pkg/chunked/zstdchunked_test.go index ec1edffcf2..5f7ced4687 100644 --- a/pkg/chunked/zstdchunked_test.go +++ b/pkg/chunked/zstdchunked_test.go @@ -153,7 +153,7 @@ func TestGenerateAndParseManifest(t *testing.T) { tocDigest, err := toc.GetTOCDigest(annotations) require.NoError(t, err) require.NotNil(t, tocDigest) - manifest, _, _, err := readZstdChunkedManifest(s, 8192, *tocDigest, annotations) + manifest, _, _, err := readZstdChunkedManifest(s, *tocDigest, annotations) if err != nil { t.Error(err) } From e7a3eae5cf0d2e2c9a71cad0d3f2c43335f02229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 22 Apr 2024 22:22:14 +0200 Subject: [PATCH 2/5] Make ReadFooterDataFromBlob test-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It has no non-test users any more, so decrease the size of this package (relevant to non-c/storage callers of c/image). Should not change behavior. Signed-off-by: Miloslav Trmač --- pkg/chunked/internal/compression.go | 23 -------------------- pkg/chunked/internal/compression_test.go | 27 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/pkg/chunked/internal/compression.go b/pkg/chunked/internal/compression.go index 53fb98ae2d..14897e5c20 100644 --- a/pkg/chunked/internal/compression.go +++ b/pkg/chunked/internal/compression.go @@ -8,7 +8,6 @@ import ( "archive/tar" "bytes" "encoding/binary" - "errors" "fmt" "io" "time" @@ -251,25 +250,3 @@ func ReadFooterDataFromAnnotations(annotations map[string]string) (ZstdChunkedFo } return footerData, nil } - -// ReadFooterDataFromBlob reads the zstd:chunked footer from the binary buffer. -func ReadFooterDataFromBlob(footer []byte) (ZstdChunkedFooterData, error) { - var footerData ZstdChunkedFooterData - - if len(footer) < FooterSizeSupported { - return footerData, errors.New("blob too small") - } - footerData.Offset = binary.LittleEndian.Uint64(footer[0:8]) - footerData.LengthCompressed = binary.LittleEndian.Uint64(footer[8:16]) - footerData.LengthUncompressed = binary.LittleEndian.Uint64(footer[16:24]) - footerData.ManifestType = binary.LittleEndian.Uint64(footer[24:32]) - footerData.OffsetTarSplit = binary.LittleEndian.Uint64(footer[32:40]) - footerData.LengthCompressedTarSplit = binary.LittleEndian.Uint64(footer[40:48]) - footerData.LengthUncompressedTarSplit = binary.LittleEndian.Uint64(footer[48:56]) - - // the magic number is stored in the last 8 bytes - if !bytes.Equal(ZstdChunkedFrameMagic, footer[len(footer)-len(ZstdChunkedFrameMagic):]) { - return footerData, errors.New("invalid magic number") - } - return footerData, nil -} diff --git a/pkg/chunked/internal/compression_test.go b/pkg/chunked/internal/compression_test.go index 9d4e60d47c..26c32e5382 100644 --- a/pkg/chunked/internal/compression_test.go +++ b/pkg/chunked/internal/compression_test.go @@ -4,6 +4,9 @@ package internal import ( + "bytes" + "encoding/binary" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -23,10 +26,32 @@ func TestGenerateAndReadFooter(t *testing.T) { b := footerDataToBlob(footer) assert.Len(t, b, FooterSizeSupported) - footer2, err := ReadFooterDataFromBlob(b) + footer2, err := readFooterDataFromBlob(b) if err != nil { t.Fatal(err) } assert.Equal(t, footer, footer2) } + +// readFooterDataFromBlob reads the zstd:chunked footer from the binary buffer. +func readFooterDataFromBlob(footer []byte) (ZstdChunkedFooterData, error) { + var footerData ZstdChunkedFooterData + + if len(footer) < FooterSizeSupported { + return footerData, errors.New("blob too small") + } + footerData.Offset = binary.LittleEndian.Uint64(footer[0:8]) + footerData.LengthCompressed = binary.LittleEndian.Uint64(footer[8:16]) + footerData.LengthUncompressed = binary.LittleEndian.Uint64(footer[16:24]) + footerData.ManifestType = binary.LittleEndian.Uint64(footer[24:32]) + footerData.OffsetTarSplit = binary.LittleEndian.Uint64(footer[32:40]) + footerData.LengthCompressedTarSplit = binary.LittleEndian.Uint64(footer[40:48]) + footerData.LengthUncompressedTarSplit = binary.LittleEndian.Uint64(footer[48:56]) + + // the magic number is stored in the last 8 bytes + if !bytes.Equal(ZstdChunkedFrameMagic, footer[len(footer)-len(ZstdChunkedFrameMagic):]) { + return footerData, errors.New("invalid magic number") + } + return footerData, nil +} From 2c240ca3f9266caded187a55a82f26ed226120d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 22 Apr 2024 22:25:28 +0200 Subject: [PATCH 3/5] Inline ReadFooterDataFromAnnotations into the only caller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Again, decrease the size of the compression code for c/image. We will simplify this further immediately. Should not change behavior. Signed-off-by: Miloslav Trmač --- pkg/chunked/compression_linux.go | 11 +++++++++-- pkg/chunked/internal/compression.go | 19 ------------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/pkg/chunked/compression_linux.go b/pkg/chunked/compression_linux.go index 4196c9ce62..d1d06f7340 100644 --- a/pkg/chunked/compression_linux.go +++ b/pkg/chunked/compression_linux.go @@ -139,10 +139,17 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di return nil, nil, 0, fmt.Errorf("%q annotation missing", internal.ManifestInfoKey) } - footerData, err := internal.ReadFooterDataFromAnnotations(annotations) - if err != nil { + var footerData internal.ZstdChunkedFooterData + + if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &footerData.Offset, &footerData.LengthCompressed, &footerData.LengthUncompressed, &footerData.ManifestType); err != nil { return nil, nil, 0, err } + if tarSplitInfoKeyAnnotation, found := annotations[internal.TarSplitInfoKey]; found { + if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &footerData.OffsetTarSplit, &footerData.LengthCompressedTarSplit, &footerData.LengthUncompressedTarSplit); err != nil { + return nil, nil, 0, err + } + footerData.ChecksumAnnotationTarSplit = annotations[internal.TarSplitChecksumKey] + } if footerData.ManifestType != internal.ManifestTypeCRFS { return nil, nil, 0, errors.New("invalid manifest type") diff --git a/pkg/chunked/internal/compression.go b/pkg/chunked/internal/compression.go index 14897e5c20..e43082e7c2 100644 --- a/pkg/chunked/internal/compression.go +++ b/pkg/chunked/internal/compression.go @@ -231,22 +231,3 @@ func footerDataToBlob(footer ZstdChunkedFooterData) []byte { return manifestDataLE } - -// ReadFooterDataFromAnnotations reads the zstd:chunked footer data from the given annotations. -func ReadFooterDataFromAnnotations(annotations map[string]string) (ZstdChunkedFooterData, error) { - var footerData ZstdChunkedFooterData - - offsetMetadata := annotations[ManifestInfoKey] - - if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &footerData.Offset, &footerData.LengthCompressed, &footerData.LengthUncompressed, &footerData.ManifestType); err != nil { - return footerData, err - } - - if tarSplitInfoKeyAnnotation, found := annotations[TarSplitInfoKey]; found { - if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &footerData.OffsetTarSplit, &footerData.LengthCompressedTarSplit, &footerData.LengthUncompressedTarSplit); err != nil { - return footerData, err - } - footerData.ChecksumAnnotationTarSplit = annotations[TarSplitChecksumKey] - } - return footerData, nil -} From 8eeec330116f7c761c69c83cbc535c1ba4e84add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 22 Apr 2024 22:37:30 +0200 Subject: [PATCH 4/5] Don't use ZstdChunkedFooterData in readZstdChunkedManifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace it by individual variables. Then formally deprecate the ChecksumAnnotationTarSplit field. Should not change behavior. Signed-off-by: Miloslav Trmač --- pkg/chunked/compression_linux.go | 41 +++++++++++++++-------------- pkg/chunked/internal/compression.go | 3 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/chunked/compression_linux.go b/pkg/chunked/compression_linux.go index d1d06f7340..8fe21a90f8 100644 --- a/pkg/chunked/compression_linux.go +++ b/pkg/chunked/compression_linux.go @@ -138,42 +138,43 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di if offsetMetadata == "" { return nil, nil, 0, fmt.Errorf("%q annotation missing", internal.ManifestInfoKey) } - - var footerData internal.ZstdChunkedFooterData - - if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &footerData.Offset, &footerData.LengthCompressed, &footerData.LengthUncompressed, &footerData.ManifestType); err != nil { + var manifestOffset, manifestLengthCompressed, manifestLengthUncompressed, manifestType uint64 + if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &manifestOffset, &manifestLengthCompressed, &manifestLengthUncompressed, &manifestType); err != nil { return nil, nil, 0, err } + // The tarSplit… values are valid if tarSplitOffset > 0 + var tarSplitOffset, tarSplitLengthCompressed, tarSplitLengthUncompressed uint64 + var tarSplitChecksum string if tarSplitInfoKeyAnnotation, found := annotations[internal.TarSplitInfoKey]; found { - if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &footerData.OffsetTarSplit, &footerData.LengthCompressedTarSplit, &footerData.LengthUncompressedTarSplit); err != nil { + if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &tarSplitOffset, &tarSplitLengthCompressed, &tarSplitLengthUncompressed); err != nil { return nil, nil, 0, err } - footerData.ChecksumAnnotationTarSplit = annotations[internal.TarSplitChecksumKey] + tarSplitChecksum = annotations[internal.TarSplitChecksumKey] } - if footerData.ManifestType != internal.ManifestTypeCRFS { + if manifestType != internal.ManifestTypeCRFS { return nil, nil, 0, errors.New("invalid manifest type") } // set a reasonable limit - if footerData.LengthCompressed > (1<<20)*50 { + if manifestLengthCompressed > (1<<20)*50 { return nil, nil, 0, errors.New("manifest too big") } - if footerData.LengthUncompressed > (1<<20)*50 { + if manifestLengthUncompressed > (1<<20)*50 { return nil, nil, 0, errors.New("manifest too big") } chunk := ImageSourceChunk{ - Offset: footerData.Offset, - Length: footerData.LengthCompressed, + Offset: manifestOffset, + Length: manifestLengthCompressed, } chunks := []ImageSourceChunk{chunk} - if footerData.OffsetTarSplit > 0 { + if tarSplitOffset > 0 { chunkTarSplit := ImageSourceChunk{ - Offset: footerData.OffsetTarSplit, - Length: footerData.LengthCompressedTarSplit, + Offset: tarSplitOffset, + Length: tarSplitLengthCompressed, } chunks = append(chunks, chunkTarSplit) } @@ -203,28 +204,28 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di return blob, nil } - manifest, err := readBlob(footerData.LengthCompressed) + manifest, err := readBlob(manifestLengthCompressed) if err != nil { return nil, nil, 0, err } - decodedBlob, err := decodeAndValidateBlob(manifest, footerData.LengthUncompressed, tocDigest.String()) + decodedBlob, err := decodeAndValidateBlob(manifest, manifestLengthUncompressed, tocDigest.String()) if err != nil { return nil, nil, 0, err } decodedTarSplit := []byte{} - if footerData.OffsetTarSplit > 0 { - tarSplit, err := readBlob(footerData.LengthCompressedTarSplit) + if tarSplitOffset > 0 { + tarSplit, err := readBlob(tarSplitLengthCompressed) if err != nil { return nil, nil, 0, err } - decodedTarSplit, err = decodeAndValidateBlob(tarSplit, footerData.LengthUncompressedTarSplit, footerData.ChecksumAnnotationTarSplit) + decodedTarSplit, err = decodeAndValidateBlob(tarSplit, tarSplitLengthUncompressed, tarSplitChecksum) if err != nil { return nil, nil, 0, err } } - return decodedBlob, decodedTarSplit, int64(footerData.Offset), err + return decodedBlob, decodedTarSplit, int64(manifestOffset), err } func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompressedChecksum string) ([]byte, error) { diff --git a/pkg/chunked/internal/compression.go b/pkg/chunked/internal/compression.go index e43082e7c2..52c9ce341a 100644 --- a/pkg/chunked/internal/compression.go +++ b/pkg/chunked/internal/compression.go @@ -185,7 +185,6 @@ func WriteZstdChunkedManifest(dest io.Writer, outMetadata map[string]string, off OffsetTarSplit: uint64(tarSplitOffset), LengthCompressedTarSplit: uint64(len(tarSplitData.Data)), LengthUncompressedTarSplit: uint64(tarSplitData.UncompressedSize), - ChecksumAnnotationTarSplit: "", // unused } manifestDataLE := footerDataToBlob(footer) @@ -214,7 +213,7 @@ type ZstdChunkedFooterData struct { OffsetTarSplit uint64 LengthCompressedTarSplit uint64 LengthUncompressedTarSplit uint64 - ChecksumAnnotationTarSplit string // Only used when reading a layer, not when creating it + ChecksumAnnotationTarSplit string // Deprecated: This field is not a part of the footer and not used for any purpose. } func footerDataToBlob(footer ZstdChunkedFooterData) []byte { From ab5e27004b154d4f52c199c8ec8e68a9eeef87c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 22 Apr 2024 22:43:27 +0200 Subject: [PATCH 5/5] Shorten readZstdChunkedManifest a bit further MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have the ImageSourceChunk data type, and we already construct these values, so scan into them directly instead of having three separate variables for the two bits of data. Should not change behavior. Signed-off-by: Miloslav Trmač --- pkg/chunked/compression_linux.go | 39 ++++++++++++-------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/pkg/chunked/compression_linux.go b/pkg/chunked/compression_linux.go index 8fe21a90f8..3499acbd18 100644 --- a/pkg/chunked/compression_linux.go +++ b/pkg/chunked/compression_linux.go @@ -138,15 +138,17 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di if offsetMetadata == "" { return nil, nil, 0, fmt.Errorf("%q annotation missing", internal.ManifestInfoKey) } - var manifestOffset, manifestLengthCompressed, manifestLengthUncompressed, manifestType uint64 - if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &manifestOffset, &manifestLengthCompressed, &manifestLengthUncompressed, &manifestType); err != nil { + var manifestChunk ImageSourceChunk + var manifestLengthUncompressed, manifestType uint64 + if _, err := fmt.Sscanf(offsetMetadata, "%d:%d:%d:%d", &manifestChunk.Offset, &manifestChunk.Length, &manifestLengthUncompressed, &manifestType); err != nil { return nil, nil, 0, err } - // The tarSplit… values are valid if tarSplitOffset > 0 - var tarSplitOffset, tarSplitLengthCompressed, tarSplitLengthUncompressed uint64 + // The tarSplit… values are valid if tarSplitChunk.Offset > 0 + var tarSplitChunk ImageSourceChunk + var tarSplitLengthUncompressed uint64 var tarSplitChecksum string if tarSplitInfoKeyAnnotation, found := annotations[internal.TarSplitInfoKey]; found { - if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &tarSplitOffset, &tarSplitLengthCompressed, &tarSplitLengthUncompressed); err != nil { + if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &tarSplitChunk.Offset, &tarSplitChunk.Length, &tarSplitLengthUncompressed); err != nil { return nil, nil, 0, err } tarSplitChecksum = annotations[internal.TarSplitChecksumKey] @@ -157,28 +159,17 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di } // set a reasonable limit - if manifestLengthCompressed > (1<<20)*50 { + if manifestChunk.Length > (1<<20)*50 { return nil, nil, 0, errors.New("manifest too big") } if manifestLengthUncompressed > (1<<20)*50 { return nil, nil, 0, errors.New("manifest too big") } - chunk := ImageSourceChunk{ - Offset: manifestOffset, - Length: manifestLengthCompressed, - } - - chunks := []ImageSourceChunk{chunk} - - if tarSplitOffset > 0 { - chunkTarSplit := ImageSourceChunk{ - Offset: tarSplitOffset, - Length: tarSplitLengthCompressed, - } - chunks = append(chunks, chunkTarSplit) + chunks := []ImageSourceChunk{manifestChunk} + if tarSplitChunk.Offset > 0 { + chunks = append(chunks, tarSplitChunk) } - parts, errs, err := blobStream.GetBlobAt(chunks) if err != nil { return nil, nil, 0, err @@ -204,7 +195,7 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di return blob, nil } - manifest, err := readBlob(manifestLengthCompressed) + manifest, err := readBlob(manifestChunk.Length) if err != nil { return nil, nil, 0, err } @@ -214,8 +205,8 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di return nil, nil, 0, err } decodedTarSplit := []byte{} - if tarSplitOffset > 0 { - tarSplit, err := readBlob(tarSplitLengthCompressed) + if tarSplitChunk.Offset > 0 { + tarSplit, err := readBlob(tarSplitChunk.Length) if err != nil { return nil, nil, 0, err } @@ -225,7 +216,7 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di return nil, nil, 0, err } } - return decodedBlob, decodedTarSplit, int64(manifestOffset), err + return decodedBlob, decodedTarSplit, int64(manifestChunk.Offset), err } func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompressedChecksum string) ([]byte, error) {