CVE-2023-33957 (Go, published 2023-06-06T19:15Z)
Description: value: notation is a CLI tool to sign and verify OCI artifacts and container images. An attacker who has compromised a registry and added a high number of signatures to an artifact can cause denial of service of services on the machine, if a user runs notation inspect command on the same machine. The problem has been fixed in the release v1.0.0-rc.6. Users should upgrade their notation packages to v1.0.0-rc.6 or above. Users are advised to upgrade. Users unable to upgrade may restrict container registries to a set of secure and trusted container registries.
Severity: nan (CVSS v3 base score: 5.7)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/notaryproject/notation
Files changed:
cmd/notation/inspect.go
cmd/notation/inspect_test.go
cmd/notation/internal/errors/errors.go
cmd/notation/list.go
cmd/notation/list_test.go
cmd/notation/verify.go
cmd/notation/verify_test.go
specs/commandline/inspect.md
specs/commandline/list.md
specs/commandline/verify.md
Commit message: Merge pull request from GHSA-9m3v-v4r5-ppx7

* added max number of signatures check

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* fix

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* fix: optimize listSignatures

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update listOpts fields order

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* minor fix on error message

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

---------

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Co-authored-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Junjie Gao <junjiegao@microsoft.com>

Vulnerable code:
	err = sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
				skippedSignatures = true
				continue
			}
			sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			envelopeContent, err := sigEnvelope.Content()
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			sig := signatureOutput{
				MediaType:             sigDesc.MediaType,
				Digest:                sigManifestDesc.Digest.String(),
				SignatureAlgorithm:    string(signatureAlgorithm),
				SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
				UserDefinedAttributes: signedArtifactDesc.Annotations,
				UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
				Certificates:          getCertificates(opts.outputFormat, envelopeContent),
				SignedArtifact:        *signedArtifactDesc,
			}
			// clearing annotations from the SignedArtifact field since they're already
			// displayed as UserDefinedAttributes
			sig.SignedArtifact.Annotations = nil
			output.Signatures = append(output.Signatures, sig)
		}

	if err != nil {
	err = printOutput(opts.outputFormat, resolvedRef, output)
	if err != nil {
		outputFormat: cmd.OutputPlaintext,
		outputFormat: cmd.OutputJSON,
	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef)
func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string) error {
	err := sigRepo.ListSignatures(ctx, targetDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			if prevDigest != "" {
				// check and print title
				printTitle()

				// print each signature digest
				fmt.Printf("    ├── %s\n", prevDigest)
			}
			prevDigest = sigManifestDesc.Digest

	if err != nil {
		return err
	}

		// check and print title

		// print last signature digest
	"math"
const maxSignatureAttempts = math.MaxInt64

	reference         string
	pluginConfig      []string
	userMetadata      []string
	allowReferrersAPI bool
	ociLayout         bool
	trustPolicyScope  string
	inputType         inputType
		ArtifactReference: intendedRef,
		PluginConfig:      configs,
		// TODO: need to change MaxSignatureAttempts as a user input flag or
		// a field in config.json
		MaxSignatureAttempts: maxSignatureAttempts,
		pluginConfig: []string{"key1=val1"},
		pluginConfig: []string{"key1=val1", "key2=val2"},

Fixed code:
	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
	maxSignatures     int
			if opts.maxSignatures <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
			}
	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
	err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error {
		sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
			skippedSignatures = true
			return nil
		}
		sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		envelopeContent, err := sigEnvelope.Content()
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		sig := signatureOutput{
			MediaType:             sigDesc.MediaType,
			Digest:                sigManifestDesc.Digest.String(),
			SignatureAlgorithm:    string(signatureAlgorithm),
			SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
			UserDefinedAttributes: signedArtifactDesc.Annotations,
			UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
			Certificates:          getCertificates(opts.outputFormat, envelopeContent),
			SignedArtifact:        *signedArtifactDesc,
		}
		// clearing annotations from the SignedArtifact field since they're already
		// displayed as UserDefinedAttributes
		sig.SignedArtifact.Annotations = nil

		output.Signatures = append(output.Signatures, sig)
	var errorExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
	if err != nil && !errors.As(err, &errorExceedMaxSignatures) {
	if err := printOutput(opts.outputFormat, resolvedRef, output); err != nil {
	if errorExceedMaxSignatures.MaxSignatures > 0 {
		fmt.Println("Warning:", errorExceedMaxSignatures)
	}

		outputFormat:  cmd.OutputPlaintext,
		maxSignatures: 100,
		outputFormat:  cmd.OutputJSON,
		maxSignatures: 100,
import "fmt"


// ErrorExceedMaxSignatures is used when the number of signatures has surpassed
// the maximum limit that can be evaluated.
type ErrorExceedMaxSignatures struct {
	MaxSignatures int
}

func (e ErrorExceedMaxSignatures) Error() string {
	return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures)
}
	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
	maxSignatures     int
			if opts.maxSignatures <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
			}
	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef, opts.maxSignatures)
func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string, maxSigs int) error {
	err := listSignatures(ctx, sigRepo, targetDesc, maxSigs, func(sigManifestDesc ocispec.Descriptor) error {
		// print the previous signature digest
		if prevDigest != "" {
			printTitle()
			fmt.Printf("    ├── %s\n", prevDigest)
		prevDigest = sigManifestDesc.Digest
	// print the last signature digest
	if err != nil {
		var errExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
		if !errors.As(err, &errExceedMaxSignatures) {
			return err
		}
		fmt.Println("Warning:", errExceedMaxSignatures)
	}

// listSignatures lists signatures associated with manifestDesc with number of
// signatures limited by maxSig
func listSignatures(ctx context.Context, sigRepo notationregistry.Repository, manifestDesc ocispec.Descriptor, maxSig int, fn func(sigManifest ocispec.Descriptor) error) error {
	numOfSignatureProcessed := 0
	return sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			if numOfSignatureProcessed >= maxSig {
				return cmderr.ErrorExceedMaxSignatures{MaxSignatures: maxSig}
			}
			numOfSignatureProcessed++
			if err := fn(sigManifestDesc); err != nil {
				return err
			}
		}
		return nil
	})
}
		maxSignatures: 100,
		maxSignatures: 100,
	reference            string
	pluginConfig         []string
	userMetadata         []string
	allowReferrersAPI    bool
	ociLayout            bool
	trustPolicyScope     string
	inputType            inputType
	maxSignatureAttempts int
			if opts.maxSignatureAttempts <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatureAttempts)
			}
	command.Flags().IntVar(&opts.maxSignatureAttempts, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
		ArtifactReference:    intendedRef,
		PluginConfig:         configs,
		MaxSignatureAttempts: opts.maxSignatureAttempts,
		pluginConfig:         []string{"key1=val1"},
		maxSignatureAttempts: 100,
		pluginConfig:         []string{"key1=val1", "key2=val2"},
		maxSignatureAttempts: 100,
      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
       --max-signatures int          maximum number of signatures to evaluate or examine (default 100)