@@ -57,15 +57,20 @@ type storageImageDestination struct {
5757 stubs.AlwaysSupportsSignatures
5858
5959 imageRef storageReference
60- directory string // Temporary directory where we store blobs until Commit() time
61- nextTempFileID atomic.Int32 // A counter that we use for computing filenames to assign to blobs
62- manifest []byte // (Per-instance) manifest contents, or nil if not yet known.
63- manifestMIMEType string // Valid if manifest != nil
64- manifestDigest digest.Digest // Valid if manifest != nil
65- untrustedDiffIDValues []digest.Digest // From config’s RootFS.DiffIDs (not even validated to be valid digest.Digest!); or nil if not read yet
66- signatures []byte // Signature contents, temporary
67- signatureses map [digest.Digest ][]byte // Instance signature contents, temporary
68- metadata storageImageMetadata // Metadata contents being built
60+ directory string // Temporary directory where we store blobs until Commit() time
61+ nextTempFileID atomic.Int32 // A counter that we use for computing filenames to assign to blobs
62+ manifest []byte // (Per-instance) manifest contents, or nil if not yet known.
63+ manifestMIMEType string // Valid if manifest != nil
64+ manifestDigest digest.Digest // Valid if manifest != nil
65+ untrustedDiffIDValues []digest.Digest // From config’s RootFS.DiffIDs (not even validated to be valid digest.Digest!); or nil if not read yet
66+ // filteredLayerIndices are layer indices marked empty by FilterLayers.
67+ // When set, the full-digest mitigation is skipped for remaining layers,
68+ // and falling back from partial to ordinary layer download is refused
69+ // (because the mitigation would be the only safety net for that path).
70+ filteredLayerIndices []int
71+ signatures []byte // Signature contents, temporary
72+ signatureses map [digest.Digest ][]byte // Instance signature contents, temporary
73+ metadata storageImageMetadata // Metadata contents being built
6974
7075 // Mapping from layer (by index) to the associated ID in the storage.
7176 // It's protected *implicitly* since `commitLayer()`, at any given
@@ -221,6 +226,17 @@ func (s *storageImageDestination) NoteOriginalOCIConfig(ociConfig *imgspecv1.Ima
221226 return nil
222227}
223228
229+ // FilterLayers inspects the manifest layer infos and returns indices of layers
230+ // that should be skipped. For c/storage, this detects the zstd:chunked
231+ // sentinel layer and records that the full-digest mitigation can be skipped.
232+ func (s * storageImageDestination ) FilterLayers (layerInfos []manifest.LayerInfo ) []int {
233+ if len (layerInfos ) > 0 && layerInfos [0 ].Digest == toc .ZstdChunkedSentinelDigest {
234+ s .filteredLayerIndices = []int {0 }
235+ return s .filteredLayerIndices
236+ }
237+ return nil
238+ }
239+
224240// PutBlobWithOptions writes contents of stream and returns data representing the result.
225241// inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents.
226242// inputInfo.Size is the expected length of stream, if known.
@@ -396,6 +412,9 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces
396412 return private.UploadedBlob {}, fmt .Errorf ("internal error: in PutBlobPartial, untrustedLayerDiffID returned errUntrustedLayerDiffIDNotYetAvailable" )
397413 case errors .As (err , & diffIDUnknownErr ):
398414 if inputTOCDigest != nil {
415+ if len (s .filteredLayerIndices ) > 0 {
416+ return private.UploadedBlob {}, fmt .Errorf ("zstd:chunked sentinel present, refusing fallback to ordinary layer download: %w" , err )
417+ }
399418 return private.UploadedBlob {}, private .NewErrFallbackToOrdinaryLayerDownload (err )
400419 }
401420 untrustedDiffID = "" // A schema1 image or a non-TOC layer with no ambiguity, let it through
@@ -415,6 +434,10 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces
415434 defer func () {
416435 var perr chunked.ErrFallbackToOrdinaryLayerDownload
417436 if errors .As (retErr , & perr ) {
437+ if len (s .filteredLayerIndices ) > 0 {
438+ retErr = fmt .Errorf ("zstd:chunked sentinel present, refusing fallback to ordinary layer download: %w" , retErr )
439+ return
440+ }
418441 retErr = private .NewErrFallbackToOrdinaryLayerDownload (retErr )
419442 }
420443 }()
@@ -424,6 +447,16 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces
424447 return private.UploadedBlob {}, err
425448 }
426449 defer differ .Close ()
450+ if len (s .filteredLayerIndices ) > 0 {
451+ // Skipping the mitigation is safe because the sentinel layer guarantees
452+ // that only TOC-aware clients will process this image. With the mitigation
453+ // skipped, we also refuse any fallback to ordinary layer download (see the
454+ // checks above and the deferred error handler).
455+ logrus .Debugf ("Sentinel detected for layer %s: skipping full-digest mitigation" , srcInfo .Digest )
456+ if err := chunked .SkipMitigation (differ ); err != nil {
457+ return private.UploadedBlob {}, err
458+ }
459+ }
427460
428461 out , err := s .imageRef .transport .store .PrepareStagedLayer (nil , differ )
429462 if err != nil {
@@ -795,7 +828,7 @@ func (s *storageImageDestination) computeID(m manifest.Manifest) (string, error)
795828 case * manifest.Schema1 :
796829 // Build a list of the diffIDs we've generated for the non-throwaway FS layers
797830 for i , li := range layerInfos {
798- if li .EmptyLayer {
831+ if li .EmptyLayer || slices . Contains ( s . filteredLayerIndices , i ) {
799832 continue
800833 }
801834 trusted , ok := s .trustedLayerIdentityDataLocked (i , li .Digest )
@@ -839,6 +872,9 @@ func (s *storageImageDestination) computeID(m manifest.Manifest) (string, error)
839872 tocIDInput := ""
840873 hasLayerPulledByTOC := false
841874 for i , li := range layerInfos {
875+ if li .EmptyLayer || slices .Contains (s .filteredLayerIndices , i ) {
876+ continue
877+ }
842878 trusted , ok := s .trustedLayerIdentityDataLocked (i , li .Digest )
843879 if ! ok { // We have already committed all layers if we get to this point, so the data must have been available.
844880 return "" , fmt .Errorf ("internal inconsistency: layer (%d, %q) not found" , i , li .Digest )
@@ -1453,9 +1489,10 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options
14531489
14541490 // Extract, commit, or find the layers.
14551491 for i , blob := range layerBlobs {
1492+ isFiltered := slices .Contains (s .filteredLayerIndices , i )
14561493 if stopQueue , err := s .commitLayer (i , addedLayerInfo {
14571494 digest : blob .Digest ,
1458- emptyLayer : blob .EmptyLayer ,
1495+ emptyLayer : blob .EmptyLayer || isFiltered ,
14591496 }, blob .Size ); err != nil {
14601497 return err
14611498 } else if stopQueue {
0 commit comments