VYPR
Moderate severityNVD Advisory· Published Apr 10, 2024· Updated Aug 2, 2024

Cosign vulnerable to machine-wide denial of service via malicious artifacts

CVE-2024-29903

Description

Cosign provides code signing and transparency for containers and binaries. Prior to version 2.2.4, maliciously-crafted software artifacts can cause denial of service of the machine running Cosign thereby impacting all services on the machine. The root cause is that Cosign creates slices based on the number of signatures, manifests or attestations in untrusted artifacts. As such, the untrusted artifact can control the amount of memory that Cosign allocates. The exact issue is Cosign allocates excessive memory on the lines that creates a slice of the same length as the manifests. Version 2.2.4 contains a patch for the vulnerability.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/sigstore/cosignGo
<= 2.2.3
github.com/sigstore/cosign/v2Go
< 2.2.42.2.4

Affected products

1

Patches

1
629f5f8fa672

Fixes for GHSA-88jx-383q-w4qc and GHSA-95pr-fxf5-86gv (#3661)

https://github.com/sigstore/cosignHayden BApr 10, 2024via ghsa
22 files changed · +657 7
  • cmd/cosign/cli/verify/verify_blob_attestation.go+9 0 modified
    @@ -34,6 +34,7 @@ import (
     	"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
     	"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
     	internal "github.com/sigstore/cosign/v2/internal/pkg/cosign"
    +	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
     	"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
     	"github.com/sigstore/cosign/v2/pkg/blob"
     	"github.com/sigstore/cosign/v2/pkg/cosign"
    @@ -117,6 +118,14 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
     			return err
     		}
     		defer f.Close()
    +		fileInfo, err := f.Stat()
    +		if err != nil {
    +			return err
    +		}
    +		err = payloadsize.CheckSize(uint64(fileInfo.Size()))
    +		if err != nil {
    +			return err
    +		}
     
     		payload = internal.NewHashReader(f, sha256.New())
     		if _, err := io.ReadAll(&payload); err != nil {
    
  • cmd/cosign/cli/verify/verify_blob_attestation_test.go+12 0 modified
    @@ -32,6 +32,7 @@ gZPFIp557+TOoDxf14FODWc+sIPETk0OgCplAk60doVXbCv33IU4rXZHrg==
     const (
     	blobContents                         = "some-payload"
     	anotherBlobContents                  = "another-blob"
    +	hugeBlobContents                     = "hugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayload"
     	blobSLSAProvenanceSignature          = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJalkxT0RjNE1XTmtOR1ZrT1dKallUWXdaR0ZqWkRBNVpqZGlZamt4TkdKaU5URTFNREpsT0dJMVpEWXhPV1kxTjJZek9XRXhaRFkxTWpVNU5tTmpNalFpZlgxZExDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJaklpZlN3aVluVnBiR1JVZVhCbElqb2llQ0lzSW1sdWRtOWpZWFJwYjI0aU9uc2lZMjl1Wm1sblUyOTFjbU5sSWpwN2ZYMTlmUT09Iiwic2lnbmF0dXJlcyI6W3sia2V5aWQiOiIiLCJzaWciOiJNRVVDSUE4S2pacWtydDkwZnpCb2pTd3d0ajNCcWI0MUU2cnV4UWs5N1RMbnB6ZFlBaUVBek9Bak9Uenl2VEhxYnBGREFuNnpocmc2RVp2N2t4SzVmYVJvVkdZTWgyYz0ifV19"
     	dssePredicateEmptySubject            = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHRkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FWUNJUUNrTEV2NkhZZ0svZDdUK0N3NTdXbkZGaHFUTC9WalAyVDA5Q2t1dk1nbDRnSWhBT1hBM0lhWWg1M1FscVk1eVU4cWZxRXJma2tGajlEakZnaWovUTQ2NnJSViJ9XX0="
     	dssePredicateMissingSha256           = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdmWDFkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FVUNJQysvM2M4RFo1TGFZTEx6SFZGejE3ZmxHUENlZXVNZ2tIKy8wa2s1cFFLUEFpRUFqTStyYnBBRlJybDdpV0I2Vm9BYVZPZ3U3NjRRM0JKdHI1bHk4VEFHczNrPSJ9XX0="
    @@ -46,13 +47,15 @@ func TestVerifyBlobAttestation(t *testing.T) {
     
     	blobPath := writeBlobFile(t, td, blobContents, "blob")
     	anotherBlobPath := writeBlobFile(t, td, anotherBlobContents, "other-blob")
    +	hugeBlobPath := writeBlobFile(t, td, hugeBlobContents, "huge-blob")
     	keyRef := writeBlobFile(t, td, pubkey, "cosign.pub")
     
     	tests := []struct {
     		description   string
     		blobPath      string
     		signature     string
     		predicateType string
    +		env           map[string]string
     		shouldErr     bool
     	}{
     		{
    @@ -98,11 +101,20 @@ func TestVerifyBlobAttestation(t *testing.T) {
     			signature:     dssePredicateMultipleSubjectsInvalid,
     			blobPath:      blobPath,
     			shouldErr:     true,
    +		}, {
    +			description: "override file size limit",
    +			signature:   blobSLSAProvenanceSignature,
    +			blobPath:    hugeBlobPath,
    +			env:         map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "128"},
    +			shouldErr:   true,
     		},
     	}
     
     	for _, test := range tests {
     		t.Run(test.description, func(t *testing.T) {
    +			for k, v := range test.env {
    +				t.Setenv(k, v)
    +			}
     			decodedSig, err := base64.StdEncoding.DecodeString(test.signature)
     			if err != nil {
     				t.Fatal(err)
    
  • go.mod+1 0 modified
    @@ -11,6 +11,7 @@ require (
     	github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46
     	github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936
     	github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7
    +	github.com/dustin/go-humanize v1.0.1
     	github.com/go-openapi/runtime v0.28.0
     	github.com/go-openapi/strfmt v0.23.0
     	github.com/go-openapi/swag v0.23.0
    
  • internal/pkg/cosign/payload/size/errors.go+31 0 added
    @@ -0,0 +1,31 @@
    +// Copyright 2024 The Sigstore Authors.
    +//
    +// 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.
    +
    +package payload
    +
    +import "fmt"
    +
    +// MaxLayerSizeExceeded is an error indicating that the layer is too big to read into memory and cosign should abort processing it.
    +type MaxLayerSizeExceeded struct {
    +	value   uint64
    +	maximum uint64
    +}
    +
    +func NewMaxLayerSizeExceeded(value, maximum uint64) *MaxLayerSizeExceeded {
    +	return &MaxLayerSizeExceeded{value, maximum}
    +}
    +
    +func (e *MaxLayerSizeExceeded) Error() string {
    +	return fmt.Sprintf("size of layer (%d) exceeded the limit (%d)", e.value, e.maximum)
    +}
    
  • internal/pkg/cosign/payload/size/size.go+38 0 added
    @@ -0,0 +1,38 @@
    +// Copyright 2024 The Sigstore Authors.
    +//
    +// 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.
    +
    +package payload
    +
    +import (
    +	"github.com/dustin/go-humanize"
    +	"github.com/sigstore/cosign/v2/pkg/cosign/env"
    +)
    +
    +const defaultMaxSize = uint64(134217728) // 128MiB
    +
    +func CheckSize(size uint64) error {
    +	maxSize := defaultMaxSize
    +	maxSizeOverride, exists := env.LookupEnv(env.VariableMaxAttachmentSize)
    +	if exists {
    +		var err error
    +		maxSize, err = humanize.ParseBytes(maxSizeOverride)
    +		if err != nil {
    +			maxSize = defaultMaxSize
    +		}
    +	}
    +	if size > maxSize {
    +		return NewMaxLayerSizeExceeded(size, maxSize)
    +	}
    +	return nil
    +}
    
  • internal/pkg/cosign/payload/size/size_test.go+110 0 added
    @@ -0,0 +1,110 @@
    +// Copyright 2024 The Sigstore Authors.
    +//
    +// 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.
    +
    +package payload
    +
    +import (
    +	"testing"
    +)
    +
    +func TestCheckSize(t *testing.T) {
    +	tests := []struct {
    +		name    string
    +		input   uint64
    +		setting string
    +		wantErr bool
    +	}{
    +		{
    +			name:    "size is within default limit",
    +			input:   1000,
    +			wantErr: false,
    +		},
    +		{
    +			name:    "size exceeds default limit",
    +			input:   200000000,
    +			wantErr: true,
    +		},
    +		{
    +			name:    "size is within overridden limit (bytes)",
    +			input:   1000,
    +			setting: "1024",
    +			wantErr: false,
    +		},
    +		{
    +			name:    "size is exceeds overridden limit (bytes)",
    +			input:   2000,
    +			setting: "1024",
    +			wantErr: true,
    +		},
    +		{
    +			name:    "size is within overridden limit (megabytes, short form)",
    +			input:   1999999,
    +			setting: "2M",
    +			wantErr: false,
    +		},
    +		{
    +			name:    "size exceeds overridden limit (megabytes, short form)",
    +			input:   2000001,
    +			setting: "2M",
    +			wantErr: true,
    +		},
    +		{
    +			name:    "size is within overridden limit (megabytes, long form)",
    +			input:   1999999,
    +			setting: "2MB",
    +			wantErr: false,
    +		},
    +		{
    +			name:    "size exceeds overridden limit (megabytes, long form)",
    +			input:   2000001,
    +			setting: "2MB",
    +			wantErr: true,
    +		},
    +		{
    +			name:    "size is within overridden limit (mebibytes)",
    +			input:   2097151,
    +			setting: "2MiB",
    +			wantErr: false,
    +		},
    +		{
    +			name:    "size exceeds overridden limit (mebibytes)",
    +			input:   2097153,
    +			setting: "2MiB",
    +			wantErr: true,
    +		},
    +		{
    +			name:    "size is negative results in default",
    +			input:   5121,
    +			setting: "-5KiB",
    +			wantErr: false,
    +		},
    +		{
    +			name:    "invalid setting results in default",
    +			input:   5121,
    +			setting: "five kilobytes",
    +			wantErr: false,
    +		},
    +	}
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			if test.setting != "" {
    +				t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", test.setting)
    +			}
    +			got := CheckSize(test.input)
    +			if (got != nil) != (test.wantErr) {
    +				t.Errorf("CheckSize() = %v, expected %v", got, test.wantErr)
    +			}
    +		})
    +	}
    +}
    
  • pkg/cosign/env/env.go+6 0 modified
    @@ -51,6 +51,7 @@ const (
     	VariablePKCS11ModulePath        Variable = "COSIGN_PKCS11_MODULE_PATH"
     	VariablePKCS11IgnoreCertificate Variable = "COSIGN_PKCS11_IGNORE_CERTIFICATE"
     	VariableRepository              Variable = "COSIGN_REPOSITORY"
    +	VariableMaxAttachmentSize       Variable = "COSIGN_MAX_ATTACHMENT_SIZE"
     
     	// Sigstore environment variables
     	VariableSigstoreCTLogPublicKeyFile Variable = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE"
    @@ -113,6 +114,11 @@ var (
     			Expects:     "string with a repository",
     			Sensitive:   false,
     		},
    +		VariableMaxAttachmentSize: {
    +			Description: "maximum attachment size to download (default 128MiB)",
    +			Expects:     "human-readable unit of memory, e.g. 5120, 20K, 3M, 45MiB, 1GB",
    +			Sensitive:   false,
    +		},
     
     		VariableSigstoreCTLogPublicKeyFile: {
     			Description: "overrides what is used to validate the SCT coming back from Fulcio",
    
  • pkg/oci/errors.go+31 0 added
    @@ -0,0 +1,31 @@
    +// Copyright 2024 The Sigstore Authors.
    +//
    +// 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.
    +
    +package oci
    +
    +import "fmt"
    +
    +// MaxLayersExceeded is an error indicating that the artifact has too many layers and cosign should abort processing it.
    +type MaxLayersExceeded struct {
    +	value   int64
    +	maximum int64
    +}
    +
    +func NewMaxLayersExceeded(value, maximum int64) *MaxLayersExceeded {
    +	return &MaxLayersExceeded{value, maximum}
    +}
    +
    +func (e *MaxLayersExceeded) Error() string {
    +	return fmt.Sprintf("number of layers (%d) exceeded the limit (%d)", e.value, e.maximum)
    +}
    
  • pkg/oci/internal/signature/layer.go+9 0 modified
    @@ -24,6 +24,7 @@ import (
     	"strings"
     
     	v1 "github.com/google/go-containerregistry/pkg/v1"
    +	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
     	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
     	"github.com/sigstore/cosign/v2/pkg/oci"
     	"github.com/sigstore/sigstore/pkg/cryptoutils"
    @@ -58,6 +59,14 @@ func (s *sigLayer) Annotations() (map[string]string, error) {
     
     // Payload implements oci.Signature
     func (s *sigLayer) Payload() ([]byte, error) {
    +	size, err := s.Layer.Size()
    +	if err != nil {
    +		return nil, err
    +	}
    +	err = payloadsize.CheckSize(uint64(size))
    +	if err != nil {
    +		return nil, err
    +	}
     	// Compressed is a misnomer here, we just want the raw bytes from the registry.
     	r, err := s.Layer.Compressed()
     	if err != nil {
    
  • pkg/oci/internal/signature/layer_test.go+52 0 modified
    @@ -20,6 +20,8 @@ import (
     	"encoding/base64"
     	"errors"
     	"fmt"
    +	"io"
    +	"strings"
     	"testing"
     
     	"github.com/google/go-cmp/cmp"
    @@ -50,6 +52,7 @@ func TestSignature(t *testing.T) {
     	tests := []struct {
     		name           string
     		l              *sigLayer
    +		env            map[string]string
     		wantPayloadErr error
     		wantSig        string
     		wantSigErr     error
    @@ -222,10 +225,39 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
     		},
     		wantSig:   "blah",
     		wantChain: 1,
    +	}, {
    +		name: "payload size exceeds default limit",
    +		l: &sigLayer{
    +			Layer: &mockLayer{size: 134217728 + 42}, // 128MB + 42 bytes
    +		},
    +		wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"),
    +	}, {
    +		name: "payload size exceeds overridden limit",
    +		l: &sigLayer{
    +			Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes
    +		},
    +		env:            map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"},
    +		wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"),
    +	}, {
    +		name: "payload size is within overridden limit",
    +		l: &sigLayer{
    +			Layer: layer,
    +			desc: v1.Descriptor{
    +				Digest: digest,
    +				Annotations: map[string]string{
    +					sigkey: "blah",
    +				},
    +			},
    +		},
    +		env:     map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"},
    +		wantSig: "blah",
     	}}
     
     	for _, test := range tests {
     		t.Run(test.name, func(t *testing.T) {
    +			for k, v := range test.env {
    +				t.Setenv(k, v)
    +			}
     			b, err := test.l.Payload()
     			switch {
     			case (err != nil) != (test.wantPayloadErr != nil):
    @@ -239,6 +271,9 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
     					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
     				}
     			}
    +			if err != nil {
    +				return
    +			}
     
     			switch got, err := test.l.Base64Signature(); {
     			case (err != nil) != (test.wantSigErr != nil):
    @@ -453,3 +488,20 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
     		})
     	}
     }
    +
    +type mockLayer struct {
    +	size int64
    +}
    +
    +func (m *mockLayer) Size() (int64, error) {
    +	return m.size, nil
    +}
    +
    +func (m *mockLayer) Compressed() (io.ReadCloser, error) {
    +	return io.NopCloser(strings.NewReader("data")), nil
    +}
    +
    +func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
    +func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
    
  • pkg/oci/layout/signatures.go+7 1 modified
    @@ -21,6 +21,8 @@ import (
     	"github.com/sigstore/cosign/v2/pkg/oci/internal/signature"
     )
     
    +const maxLayers = 1000
    +
     type sigs struct {
     	v1.Image
     }
    @@ -33,7 +35,11 @@ func (s *sigs) Get() ([]oci.Signature, error) {
     	if err != nil {
     		return nil, err
     	}
    -	signatures := make([]oci.Signature, 0, len(manifest.Layers))
    +	numLayers := int64(len(manifest.Layers))
    +	if numLayers > maxLayers {
    +		return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
    +	}
    +	signatures := make([]oci.Signature, 0, numLayers)
     	for _, desc := range manifest.Layers {
     		l, err := s.Image.LayerByDigest(desc.Digest)
     		if err != nil {
    
  • pkg/oci/layout/signatures_test.go+62 0 added
    @@ -0,0 +1,62 @@
    +// Copyright 2024 The Sigstore Authors.
    +//
    +// 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.
    +
    +package layout
    +
    +import (
    +	"errors"
    +	"testing"
    +
    +	v1 "github.com/google/go-containerregistry/pkg/v1"
    +	"github.com/google/go-containerregistry/pkg/v1/fake"
    +)
    +
    +func TestGet(t *testing.T) {
    +	tests := []struct {
    +		name      string
    +		layers    int
    +		wantError error
    +	}{
    +		{
    +			name:      "within limit",
    +			layers:    23,
    +			wantError: nil,
    +		},
    +		{
    +			name:      "exceeds limit",
    +			layers:    4242,
    +			wantError: errors.New("number of layers (4242) exceeded the limit (1000)"),
    +		},
    +	}
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			s := sigs{
    +				Image: &fake.FakeImage{
    +					ManifestStub: func() (*v1.Manifest, error) {
    +						return &v1.Manifest{
    +							Layers: make([]v1.Descriptor, test.layers),
    +						}, nil
    +					},
    +				},
    +			}
    +			_, err := s.Get()
    +			if test.wantError != nil && test.wantError.Error() != err.Error() {
    +				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
    +			}
    +			if test.wantError == nil && err != nil {
    +				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
    +			}
    +		})
    +	}
    +}
    
  • pkg/oci/mutate/signatures.go+6 0 modified
    @@ -23,6 +23,8 @@ import (
     	"github.com/sigstore/cosign/v2/pkg/oci"
     )
     
    +const maxLayers = 1000
    +
     // AppendSignatures produces a new oci.Signatures with the provided signatures
     // appended to the provided base signatures.
     func AppendSignatures(base oci.Signatures, recordCreationTimestamp bool, sigs ...oci.Signature) (oci.Signatures, error) {
    @@ -106,5 +108,9 @@ func (sa *sigAppender) Get() ([]oci.Signature, error) {
     	if err != nil {
     		return nil, err
     	}
    +	sumLayers := int64(len(sl) + len(sa.sigs))
    +	if sumLayers > maxLayers {
    +		return nil, oci.NewMaxLayersExceeded(sumLayers, maxLayers)
    +	}
     	return append(sl, sa.sigs...), nil
     }
    
  • pkg/oci/mutate/signatures_test.go+63 0 modified
    @@ -16,8 +16,11 @@
     package mutate
     
     import (
    +	"errors"
     	"testing"
     
    +	v1 "github.com/google/go-containerregistry/pkg/v1"
    +	"github.com/sigstore/cosign/v2/pkg/oci"
     	"github.com/sigstore/cosign/v2/pkg/oci/empty"
     	"github.com/sigstore/cosign/v2/pkg/oci/static"
     )
    @@ -83,3 +86,63 @@ func TestAppendSignatures(t *testing.T) {
     		t.Errorf("Date of Signature was Zero")
     	}
     }
    +
    +func TestGet(t *testing.T) {
    +	tests := []struct {
    +		name         string
    +		baseLayers   int
    +		appendLayers int
    +		wantError    error
    +	}{
    +		{
    +			name:         "within limit",
    +			baseLayers:   1,
    +			appendLayers: 1,
    +			wantError:    nil,
    +		},
    +		{
    +			name:         "base exceeds limit",
    +			baseLayers:   2000,
    +			appendLayers: 1,
    +			wantError:    errors.New("number of layers (2001) exceeded the limit (1000)"),
    +		},
    +		{
    +			name:         "append exceeds limit",
    +			baseLayers:   1,
    +			appendLayers: 1300,
    +			wantError:    errors.New("number of layers (1301) exceeded the limit (1000)"),
    +		},
    +		{
    +			name:         "sum exceeds limit",
    +			baseLayers:   666,
    +			appendLayers: 666,
    +			wantError:    errors.New("number of layers (1332) exceeded the limit (1000)"),
    +		},
    +	}
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			sa := sigAppender{
    +				base: &mockOCISignatures{
    +					signatures: make([]oci.Signature, test.baseLayers),
    +				},
    +				sigs: make([]oci.Signature, test.appendLayers),
    +			}
    +			_, err := sa.Get()
    +			if test.wantError != nil && test.wantError.Error() != err.Error() {
    +				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
    +			}
    +			if test.wantError == nil && err != nil {
    +				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
    +			}
    +		})
    +	}
    +}
    +
    +type mockOCISignatures struct {
    +	v1.Image
    +	signatures []oci.Signature
    +}
    +
    +func (m *mockOCISignatures) Get() ([]oci.Signature, error) {
    +	return m.signatures, nil
    +}
    
  • pkg/oci/remote/remote.go+10 0 modified
    @@ -26,6 +26,7 @@ import (
     	"github.com/google/go-containerregistry/pkg/v1/remote"
     	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
     	"github.com/google/go-containerregistry/pkg/v1/types"
    +	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
     	ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote"
     	"github.com/sigstore/cosign/v2/pkg/oci"
     )
    @@ -226,6 +227,15 @@ func (f *attached) FileMediaType() (types.MediaType, error) {
     
     // Payload implements oci.File
     func (f *attached) Payload() ([]byte, error) {
    +	size, err := f.layer.Size()
    +	if err != nil {
    +		return nil, err
    +	}
    +	err = payloadsize.CheckSize(uint64(size))
    +	if err != nil {
    +		return nil, err
    +	}
    +
     	// remote layers are believed to be stored
     	// compressed, but we don't compress attachments
     	// so use "Compressed" to access the raw byte
    
  • pkg/oci/remote/remote_test.go+70 0 modified
    @@ -17,11 +17,14 @@ package remote
     
     import (
     	"errors"
    +	"io"
    +	"strings"
     	"testing"
     
     	"github.com/google/go-containerregistry/pkg/name"
     	v1 "github.com/google/go-containerregistry/pkg/v1"
     	"github.com/google/go-containerregistry/pkg/v1/remote"
    +	"github.com/google/go-containerregistry/pkg/v1/types"
     )
     
     func TestTagMethods(t *testing.T) {
    @@ -203,3 +206,70 @@ func TestDockercontentDigest(t *testing.T) {
     		})
     	}
     }
    +
    +func TestPayload(t *testing.T) {
    +	tests := []struct {
    +		name      string
    +		size      int64
    +		env       map[string]string
    +		wantError error
    +	}{
    +		{
    +			name:      "within default limit",
    +			size:      1000,
    +			wantError: nil,
    +		},
    +		{
    +			name:      "excceds default limit",
    +			size:      1073741824,
    +			wantError: errors.New("size of layer (1073741824) exceeded the limit (134217728)"),
    +		},
    +		{
    +			name:      "exceeds overridden limit",
    +			size:      5120,
    +			env:       map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1KB"},
    +			wantError: errors.New("size of layer (5120) exceeded the limit (1000)"),
    +		},
    +		{
    +			name: "within overridden limit",
    +			size: 5120,
    +			env:  map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "10KB"},
    +		},
    +	}
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			for k, v := range test.env {
    +				t.Setenv(k, v)
    +			}
    +			a := attached{
    +				layer: &mockLayer{
    +					size: test.size,
    +				},
    +			}
    +			_, err := a.Payload()
    +			if test.wantError != nil && test.wantError.Error() != err.Error() {
    +				t.Fatalf("Payload() = %v, wanted %v", err, test.wantError)
    +			}
    +			if test.wantError == nil && err != nil {
    +				t.Fatalf("Payload() = %v, wanted %v", err, test.wantError)
    +			}
    +		})
    +	}
    +}
    +
    +type mockLayer struct {
    +	size int64
    +}
    +
    +func (m *mockLayer) Compressed() (io.ReadCloser, error) {
    +	return io.NopCloser(strings.NewReader("test payload")), nil
    +}
    +
    +func (m *mockLayer) Size() (int64, error) {
    +	return m.size, nil
    +}
    +
    +func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
    +func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
    
  • pkg/oci/remote/signatures.go+6 0 modified
    @@ -27,6 +27,8 @@ import (
     	"github.com/sigstore/cosign/v2/pkg/oci/internal/signature"
     )
     
    +const maxLayers = 1000
    +
     // Signatures fetches the signatures image represented by the named reference.
     // If the tag is not found, this returns an empty oci.Signatures.
     func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) {
    @@ -58,6 +60,10 @@ func (s *sigs) Get() ([]oci.Signature, error) {
     	if err != nil {
     		return nil, err
     	}
    +	numLayers := int64(len(m.Layers))
    +	if numLayers > maxLayers {
    +		return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
    +	}
     	signatures := make([]oci.Signature, 0, len(m.Layers))
     	for _, desc := range m.Layers {
     		layer, err := s.Image.LayerByDigest(desc.Digest)
    
  • pkg/oci/remote/signatures_test.go+22 0 modified
    @@ -22,6 +22,7 @@ import (
     
     	"github.com/google/go-containerregistry/pkg/name"
     	v1 "github.com/google/go-containerregistry/pkg/v1"
    +	"github.com/google/go-containerregistry/pkg/v1/fake"
     	"github.com/google/go-containerregistry/pkg/v1/remote"
     	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
     )
    @@ -75,4 +76,25 @@ func TestSignaturesErrors(t *testing.T) {
     			t.Fatalf("Signatures() = %v, wanted %v", err, want)
     		}
     	})
    +
    +	t.Run("too many layers", func(t *testing.T) {
    +		remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) {
    +			return &fake.FakeImage{
    +				ManifestStub: func() (*v1.Manifest, error) {
    +					return &v1.Manifest{
    +						Layers: make([]v1.Descriptor, 10000),
    +					}, nil
    +				},
    +			}, nil
    +		}
    +		sigs, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig"))
    +		if err != nil {
    +			t.Fatalf("Signatures() = %v", err)
    +		}
    +		want := errors.New("number of layers (10000) exceeded the limit (1000)")
    +		_, err = sigs.Get()
    +		if err == nil || want.Error() != err.Error() {
    +			t.Fatalf("Get() = %v", err)
    +		}
    +	})
     }
    
  • pkg/oci/signature/layer.go+9 0 modified
    @@ -24,6 +24,7 @@ import (
     	"strings"
     
     	v1 "github.com/google/go-containerregistry/pkg/v1"
    +	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
     	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
     	"github.com/sigstore/cosign/v2/pkg/oci"
     	"github.com/sigstore/sigstore/pkg/cryptoutils"
    @@ -58,6 +59,14 @@ func (s *sigLayer) Annotations() (map[string]string, error) {
     
     // Payload implements oci.Signature
     func (s *sigLayer) Payload() ([]byte, error) {
    +	size, err := s.Layer.Size()
    +	if err != nil {
    +		return nil, err
    +	}
    +	err = payloadsize.CheckSize(uint64(size))
    +	if err != nil {
    +		return nil, err
    +	}
     	// Compressed is a misnomer here, we just want the raw bytes from the registry.
     	r, err := s.Layer.Compressed()
     	if err != nil {
    
  • pkg/oci/signature/layer_test.go+52 0 modified
    @@ -20,6 +20,8 @@ import (
     	"encoding/base64"
     	"errors"
     	"fmt"
    +	"io"
    +	"strings"
     	"testing"
     
     	"github.com/google/go-cmp/cmp"
    @@ -292,6 +294,7 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
     	tests := []struct {
     		name           string
     		l              *sigLayer
    +		env            map[string]string
     		wantPayloadErr error
     		wantSig        string
     		wantSigErr     error
    @@ -397,10 +400,39 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
     		wantBundle: &bundle.RFC3161Timestamp{
     			SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
     		},
    +	}, {
    +		name: "payload size exceeds default limit",
    +		l: &sigLayer{
    +			Layer: &mockLayer{size: 134217728 + 42}, // 128MiB + 42 bytes
    +		},
    +		wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"),
    +	}, {
    +		name: "payload size exceeds overridden limit",
    +		l: &sigLayer{
    +			Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes
    +		},
    +		env:            map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"},
    +		wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"),
    +	}, {
    +		name: "payload size is within overridden limit",
    +		l: &sigLayer{
    +			Layer: layer,
    +			desc: v1.Descriptor{
    +				Digest: digest,
    +				Annotations: map[string]string{
    +					sigkey: "blah",
    +				},
    +			},
    +		},
    +		env:     map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"},
    +		wantSig: "blah",
     	}}
     
     	for _, test := range tests {
     		t.Run(test.name, func(t *testing.T) {
    +			for k, v := range test.env {
    +				t.Setenv(k, v)
    +			}
     			b, err := test.l.Payload()
     			switch {
     			case (err != nil) != (test.wantPayloadErr != nil):
    @@ -414,6 +446,9 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
     					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
     				}
     			}
    +			if err != nil {
    +				return
    +			}
     
     			switch got, err := test.l.Base64Signature(); {
     			case (err != nil) != (test.wantSigErr != nil):
    @@ -453,3 +488,20 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
     		})
     	}
     }
    +
    +type mockLayer struct {
    +	size int64
    +}
    +
    +func (m *mockLayer) Size() (int64, error) {
    +	return m.size, nil
    +}
    +
    +func (m *mockLayer) Compressed() (io.ReadCloser, error) {
    +	return io.NopCloser(strings.NewReader("data")), nil
    +}
    +
    +func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
    +func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
    +func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
    
  • pkg/oci/static/file.go+9 0 modified
    @@ -22,6 +22,7 @@ import (
     	"github.com/google/go-containerregistry/pkg/v1/empty"
     	"github.com/google/go-containerregistry/pkg/v1/mutate"
     	"github.com/google/go-containerregistry/pkg/v1/types"
    +	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
     	"github.com/sigstore/cosign/v2/internal/pkg/now"
     	"github.com/sigstore/cosign/v2/pkg/oci"
     	"github.com/sigstore/cosign/v2/pkg/oci/signed"
    @@ -82,6 +83,14 @@ func (f *file) FileMediaType() (types.MediaType, error) {
     
     // Payload implements oci.File
     func (f *file) Payload() ([]byte, error) {
    +	size, err := f.layer.Size()
    +	if err != nil {
    +		return nil, err
    +	}
    +	err = payloadsize.CheckSize(uint64(size))
    +	if err != nil {
    +		return nil, err
    +	}
     	rc, err := f.layer.Uncompressed()
     	if err != nil {
     		return nil, err
    
  • pkg/oci/static/file_test.go+42 6 modified
    @@ -16,6 +16,7 @@
     package static
     
     import (
    +	"errors"
     	"io"
     	"strings"
     	"testing"
    @@ -27,7 +28,7 @@ import (
     
     func TestNewFile(t *testing.T) {
     	payload := "this is the content!"
    -	file, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"}))
    +	f, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"}))
     	if err != nil {
     		t.Fatalf("NewFile() = %v", err)
     	}
    @@ -38,7 +39,7 @@ func TestNewFile(t *testing.T) {
     		t.Fatalf("NewFile() = %v", err)
     	}
     
    -	layers, err := file.Layers()
    +	layers, err := f.Layers()
     	if err != nil {
     		t.Fatalf("Layers() = %v", err)
     	} else if got, want := len(layers), 1; got != want {
    @@ -59,7 +60,7 @@ func TestNewFile(t *testing.T) {
     
     	t.Run("check media type", func(t *testing.T) {
     		wantMT := types.MediaType("foo")
    -		gotMT, err := file.FileMediaType()
    +		gotMT, err := f.FileMediaType()
     		if err != nil {
     			t.Fatalf("MediaType() = %v", err)
     		}
    @@ -118,7 +119,7 @@ func TestNewFile(t *testing.T) {
     			t.Errorf("Uncompressed() = %s, wanted %s", got, want)
     		}
     
    -		gotPayload, err := file.Payload()
    +		gotPayload, err := f.Payload()
     		if err != nil {
     			t.Fatalf("Payload() = %v", err)
     		}
    @@ -128,7 +129,7 @@ func TestNewFile(t *testing.T) {
     	})
     
     	t.Run("check date", func(t *testing.T) {
    -		fileCfg, err := file.ConfigFile()
    +		fileCfg, err := f.ConfigFile()
     		if err != nil {
     			t.Fatalf("ConfigFile() = %v", err)
     		}
    @@ -145,7 +146,7 @@ func TestNewFile(t *testing.T) {
     	})
     
     	t.Run("check annotations", func(t *testing.T) {
    -		m, err := file.Manifest()
    +		m, err := f.Manifest()
     		if err != nil {
     			t.Fatalf("Manifest() = %v", err)
     		}
    @@ -154,4 +155,39 @@ func TestNewFile(t *testing.T) {
     			t.Errorf("Annotations = %s, wanted %s", got, want)
     		}
     	})
    +
    +	t.Run("huge file payload", func(t *testing.T) {
    +		// default limit
    +		f := file{
    +			layer: &mockLayer{200000000},
    +		}
    +		want := errors.New("size of layer (200000000) exceeded the limit (134217728)")
    +		_, err = f.Payload()
    +		if err == nil || want.Error() != err.Error() {
    +			t.Errorf("Payload() = %v, wanted %v", err, want)
    +		}
    +		// override limit
    +		t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", "512MiB")
    +		_, err = f.Payload()
    +		if err != nil {
    +			t.Errorf("Payload() = %v, wanted nil", err)
    +		}
    +	})
     }
    +
    +type mockLayer struct {
    +	size int64
    +}
    +
    +func (m *mockLayer) Size() (int64, error) {
    +	return m.size, nil
    +}
    +
    +func (m *mockLayer) Uncompressed() (io.ReadCloser, error) {
    +	return io.NopCloser(strings.NewReader("data")), nil
    +}
    +
    +func (m *mockLayer) Digest() (v1.Hash, error)            { panic("not implemented") }
    +func (m *mockLayer) DiffID() (v1.Hash, error)            { panic("not implemented") }
    +func (m *mockLayer) Compressed() (io.ReadCloser, error)  { panic("not implemented") }
    +func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.