CVE-2023-30551 (published 2023-05-08T16:15Z)

Rekor is an open source software supply chain transparency log. Rekor prior to version 1.1.1 may crash due to out of memory (OOM) conditions caused by reading archive metadata files into memory without checking their sizes first. Verification of a JAR file submitted to Rekor can cause an out of memory crash if files within the META-INF directory of the JAR are sufficiently large. Parsing of an APK file submitted to Rekor can cause an out of memory crash if the .SIGN or .PKGINFO files within the APK are sufficiently large. The OOM crash has been patched in Rekor version 1.1.1. There are no known workarounds.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/sigstore/rekor
commit_details: repo_url: https://github.com/sigstore/rekor
author: Ceridwen Coghlan
author_date: 2023-05-02 16:44:35-07:00
msg: Merge pull request from GHSA-2h5h-59f5-c5x9

* check size of archive metadata files before read

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

* fix your ints

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

* unit tests for metadata size errors

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

* update for other tests

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

* quick checks and comments

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

* checks + comments take 2

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>

---------

Signed-off-by: Ceridwen Coghlan <cdriskill@google.com>
repository: repo_url: https://github.com/sigstore/rekor
repo_name: sigstore/rekor
description: Software Supply Chain Transparency Log
repo_language: Go
owner: sigstore
file_changes: filename: root.go
change_type: ModificationType.MODIFY
diff: @@ -106,6 +106,8 @@ Memory and file-based signers should only be used for testing.`)
 	rootCmd.PersistentFlags().StringSlice("enabled_api_endpoints", operationIds, "list of API endpoints to enable using operationId from openapi.yaml")
 
 	rootCmd.PersistentFlags().Uint64("max_request_body_size", 0, "maximum size for HTTP request body, in bytes; set to 0 for unlimited")
+	rootCmd.PersistentFlags().Uint64("max_jar_metadata_size", 1048576, "maximum permitted size for jar META-INF/ files, in bytes; set to 0 for unlimited")
+	rootCmd.PersistentFlags().Uint64("max_apk_metadata_size", 1048576, "maximum permitted size for apk .SIGN and .PKGINFO files, in bytes; set to 0 for unlimited")
 
 	if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
 		log.Logger.Fatal(err)

programming_language: Go
filename: apk.go
change_type: ModificationType.MODIFY
diff: @@ -33,6 +33,7 @@ import (
 
 	"github.com/sigstore/sigstore/pkg/signature"
 	"github.com/sigstore/sigstore/pkg/signature/options"
+	"github.com/spf13/viper"
 	"gopkg.in/ini.v1"
 )
 
@@ -149,6 +150,12 @@ func (p *Package) Unmarshal(pkgReader io.Reader) error {
 		}
 
 		if strings.HasPrefix(header.Name, ".SIGN") && pkg.Signature == nil {
+			if header.Size < 0 {
+				return errors.New("negative header size for .SIGN file")
+			}
+			if uint64(header.Size) > viper.GetUint64("max_apk_metadata_size") && viper.GetUint64("max_apk_metadata_size") > 0 {
+				return fmt.Errorf("uncompressed .SIGN file size %d exceeds max allowed size %d", header.Size, viper.GetUint64("max_apk_metadata_size"))
+			}
 			sigBytes := make([]byte, header.Size)
 			if _, err = sigReader.Read(sigBytes); err != nil && err != io.EOF {
 				return fmt.Errorf("reading signature: %w", err)
@@ -176,6 +183,12 @@ func (p *Package) Unmarshal(pkgReader io.Reader) error {
 		}
 
 		if header.Name == ".PKGINFO" {
+			if header.Size < 0 {
+				return errors.New("negative header size for .PKGINFO file")
+			}
+			if uint64(header.Size) > viper.GetUint64("max_apk_metadata_size") && viper.GetUint64("max_apk_metadata_size") > 0 {
+				return fmt.Errorf("uncompressed .PKGINFO file size %d exceeds max allowed size %d", header.Size, viper.GetUint64("max_apk_metadata_size"))
+			}
 			pkginfoContent := make([]byte, header.Size)
 			if _, err = ctlReader.Read(pkginfoContent); err != nil && err != io.EOF {
 				return fmt.Errorf("reading .PKGINFO: %w", err)

programming_language: Go
filename: apk_test.go
change_type: ModificationType.MODIFY
diff: @@ -17,12 +17,15 @@ package alpine
 
 import (
 	"os"
+	"strings"
 	"testing"
 
 	"github.com/sigstore/rekor/pkg/pki/x509"
+	"github.com/spf13/viper"
 )
 
 func TestAlpinePackage(t *testing.T) {
+
 	inputArchive, err := os.Open("tests/test_alpine.apk")
 	if err != nil {
 		t.Fatalf("could not open archive %v", err)
@@ -48,3 +51,22 @@ func TestAlpinePackage(t *testing.T) {
 		t.Fatalf("signature verification failed: %v", err)
 	}
 }
+
+func TestAlpineMetadataSize(t *testing.T) {
+	os.Setenv("MAX_APK_METADATA_SIZE", "10")
+	viper.AutomaticEnv()
+
+	inputArchive, err := os.Open("tests/test_alpine.apk")
+	if err != nil {
+		t.Fatalf("could not open archive %v", err)
+	}
+
+	p := Package{}
+	err = p.Unmarshal(inputArchive)
+	if err == nil {
+		t.Fatal("expecting metadata too large err")
+	}
+	if !strings.Contains(err.Error(), "exceeds max allowed size 10") {
+		t.Fatalf("unexpected error %v", err)
+	}
+}

programming_language: Go
filename: entry.go
change_type: ModificationType.MODIFY
diff: @@ -45,6 +45,7 @@ import (
 	"github.com/go-openapi/swag"
 	jarutils "github.com/sassoftware/relic/lib/signjar"
 	"github.com/sigstore/rekor/pkg/generated/models"
+	"github.com/spf13/viper"
 )
 
 const (
@@ -139,6 +140,20 @@ func (v *V001Entry) fetchExternalEntities(ctx context.Context) (*pkcs7.PublicKey
 		return nil, nil, types.ValidationError(err)
 	}
 
+	// Checking that uncompressed metadata files are within acceptable bounds before reading into memory.
+	// Checks match those performed by the relic library in the jarutils.Verify method below. For example,
+	// the META-INF/MANIFEST.MF is read into memory by the relic lib, but a META-INF/LICENSE file is not.
+	for _, f := range zipReader.File {
+		dir, name := path.Split(strings.ToUpper(f.Name))
+		if dir != "META-INF/" || name == "" || strings.LastIndex(name, ".") < 0 {
+			continue
+		}
+		if f.UncompressedSize64 > viper.GetUint64("max_jar_metadata_size") && viper.GetUint64("max_jar_metadata_size") > 0 {
+			return nil, nil, types.ValidationError(
+				fmt.Errorf("uncompressed jar metadata of size %d exceeds max allowed size %d", f.UncompressedSize64, viper.GetUint64("max_jar_metadata_size")))
+		}
+	}
+
 	// this ensures that the JAR is signed and the signature verifies, as
 	// well as checks that the hashes in the signed manifest are all valid
 	jarObjs, err := jarutils.Verify(zipReader, false)

programming_language: Go
filename: entry_test.go
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@ import (
 	"context"
 	"os"
 	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/go-openapi/runtime"
@@ -27,6 +28,7 @@ import (
 	"github.com/go-openapi/swag"
 	"github.com/sigstore/rekor/pkg/generated/models"
 	"github.com/sigstore/rekor/pkg/types"
+	"github.com/spf13/viper"
 	"go.uber.org/goleak"
 )
 
@@ -150,3 +152,43 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 		}
 	}
 }
+
+func TestJarMetadataSize(t *testing.T) {
+	type TestCase struct {
+		caseDesc                  string
+		entry                     V001Entry
+		expectUnmarshalSuccess    bool
+		expectCanonicalizeSuccess bool
+		expectedVerifierSuccess   bool
+	}
+
+	jarBytes, _ := os.ReadFile("tests/test.jar")
+
+	os.Setenv("MAX_JAR_METADATA_SIZE", "10")
+	viper.AutomaticEnv()
+
+	v := V001Entry{
+		JARModel: models.JarV001Schema{
+			Archive: &models.JarV001SchemaArchive{
+				Content: strfmt.Base64(jarBytes),
+			},
+		},
+	}
+
+	r := models.Jar{
+		APIVersion: swag.String(v.APIVersion()),
+		Spec:       v.JARModel,
+	}
+
+	if err := v.Unmarshal(&r); err != nil {
+		t.Errorf("unexpected unmarshal failure: %v", err)
+	}
+
+	_, err := v.Canonicalize(context.TODO())
+	if err == nil {
+		t.Fatal("expecting metadata too large err")
+	}
+	if !strings.Contains(err.Error(), "exceeds max allowed size 10") {
+		t.Fatalf("unexpected error %v", err)
+	}
+}

programming_language: Go