CVE-2024-29903 (published 2024-04-10T23:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/sigstore/cosign
commit_details: repo_url: https://github.com/sigstore/cosign
author: Hayden B
author_date: 2024-04-10 14:57:21-07:00
msg: Fixes for GHSA-88jx-383q-w4qc and GHSA-95pr-fxf5-86gv (#3661)

* Merge pull request from GHSA-95pr-fxf5-86gv

An Image may come from an untrusted source and contain an unknown number
of signatures in the .sig manifest. A common pattern in cosign is to use
the number of signatures as the capacity for a new slice. But this means
the size of the slice is based on an unvalidated external input and
could result in cosign running out of memory.

This change adds validation for certain implementations of the
oci.Signatures Get() method to limit the number of image descriptors
returned. This way, callers can rely on the returned slice of signatures
being a reasonable size to process safely.

The limit is set to 1000, which is a generous size based on the
practical restrictions that container registries set for image manifest
size and approximations of memory allocations for signature layers.

Signed-off-by: Colleen Murphy <colleenmurphy@google.com>

* Merge pull request from GHSA-88jx-383q-w4qc

When downloading an attestation or SBOM from an external source, check
its size before reading it into memory. This protects the host from
potentially reading a maliciously large attachment into memory and
exhausting the system.

SBOMs can vary widely in size, and there could be legitimate SBOMs of up
to 700MB. However, reading a 700MB SBOM into memory would easily bring
down a small cloud VM. Moreover, most SBOMs are not going to be that
large. This change sets a reasonable default of 128MiB, and allows
overriding the default by setting the environment variable
`COSIGN_MAX_ATTACHMENT_SIZE`.

Signed-off-by: Colleen Murphy <colleenmurphy@google.com>

---------

Signed-off-by: Colleen Murphy <colleenmurphy@google.com>
repository: repo_url: https://github.com/sigstore/cosign
repo_name: sigstore/cosign
description: Code signing and transparency for containers and binaries
repo_language: Go
owner: sigstore
file_changes: filename: verify_blob_attestation.go
change_type: ModificationType.MODIFY
diff: @@ -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 {

programming_language: Go
filename: verify_blob_attestation_test.go
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ gZPFIp557+TOoDxf14FODWc+sIPETk0OgCplAk60doVXbCv33IU4rXZHrg==
 const (
 	blobContents                         = "some-payload"
 	anotherBlobContents                  = "another-blob"
+	hugeBlobContents                     = "hugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayload"
 	blobSLSAProvenanceSignature          = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJalkxT0RjNE1XTmtOR1ZrT1dKallUWXdaR0ZqWkRBNVpqZGlZamt4TkdKaU5URTFNREpsT0dJMVpEWXhPV1kxTjJZek9XRXhaRFkxTWpVNU5tTmpNalFpZlgxZExDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJaklpZlN3aVluVnBiR1JVZVhCbElqb2llQ0lzSW1sdWRtOWpZWFJwYjI0aU9uc2lZMjl1Wm1sblUyOTFjbU5sSWpwN2ZYMTlmUT09Iiwic2lnbmF0dXJlcyI6W3sia2V5aWQiOiIiLCJzaWciOiJNRVVDSUE4S2pacWtydDkwZnpCb2pTd3d0ajNCcWI0MUU2cnV4UWs5N1RMbnB6ZFlBaUVBek9Bak9Uenl2VEhxYnBGREFuNnpocmc2RVp2N2t4SzVmYVJvVkdZTWgyYz0ifV19"
 	dssePredicateEmptySubject            = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHRkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FWUNJUUNrTEV2NkhZZ0svZDdUK0N3NTdXbkZGaHFUTC9WalAyVDA5Q2t1dk1nbDRnSWhBT1hBM0lhWWg1M1FscVk1eVU4cWZxRXJma2tGajlEakZnaWovUTQ2NnJSViJ9XX0="
 	dssePredicateMissingSha256           = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdmWDFkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FVUNJQysvM2M4RFo1TGFZTEx6SFZGejE3ZmxHUENlZXVNZ2tIKy8wa2s1cFFLUEFpRUFqTStyYnBBRlJybDdpV0I2Vm9BYVZPZ3U3NjRRM0JKdHI1bHk4VEFHczNrPSJ9XX0="
@@ -46,6 +47,7 @@ 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 {
@@ -53,6 +55,7 @@ func TestVerifyBlobAttestation(t *testing.T) {
 		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)

programming_language: Go
filename: go.mod
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Go
filename: errors.go
change_type: ModificationType.ADD
diff: @@ -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)
+}

programming_language: Go
filename: size.go
change_type: ModificationType.ADD
diff: @@ -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
+}

programming_language: Go
filename: size_test.go
change_type: ModificationType.ADD
diff: @@ -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)
+			}
+		})
+	}
+}

programming_language: Go
filename: env.go
change_type: ModificationType.MODIFY
diff: @@ -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",

programming_language: Go
filename: errors.go
change_type: ModificationType.ADD
diff: @@ -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)
+}

programming_language: Go
filename: layer.go
change_type: ModificationType.MODIFY
diff: @@ -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 {

programming_language: Go
filename: layer_test.go
change_type: ModificationType.MODIFY
diff: @@ -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") }

programming_language: Go
filename: signatures.go
change_type: ModificationType.MODIFY
diff: @@ -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 {

programming_language: Go
filename: signatures_test.go
change_type: ModificationType.ADD
diff: @@ -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)
+			}
+		})
+	}
+}

programming_language: Go
filename: signatures.go
change_type: ModificationType.MODIFY
diff: @@ -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
 }

programming_language: Go
filename: signatures_test.go
change_type: ModificationType.MODIFY
diff: @@ -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
+}

programming_language: Go
filename: remote.go
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Go
filename: remote_test.go
change_type: ModificationType.MODIFY
diff: @@ -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") }

programming_language: Go
filename: signatures.go
change_type: ModificationType.MODIFY
diff: @@ -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)

programming_language: Go
filename: signatures_test.go
change_type: ModificationType.MODIFY
diff: @@ -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)
+		}
+	})
 }

programming_language: Go
filename: layer.go
change_type: ModificationType.MODIFY
diff: @@ -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 {

programming_language: Go
filename: layer_test.go
change_type: ModificationType.MODIFY
diff: @@ -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") }

programming_language: Go
filename: file.go
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Go
filename: file_test.go
change_type: ModificationType.MODIFY
diff: @@ -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") }

programming_language: Go