From 1c3494caf95c6ba91dc4200998affcb6c6a14494 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Mon, 3 Aug 2026 11:23:24 +0900 Subject: [PATCH] Reject a tar manifest whose descriptor carries no refs DescribedImage.Ref and DescribedImageIndex.Ref both return Refs[0] with no length check. Refs comes straight from the manifest.json inside a tar, so reading a bundle tar whose descriptor has an empty or absent Refs panics with index out of range [0] with length 0 Neither Ref has an error to return, and every descriptor this package writes carries exactly one ref, so check for it while parsing the manifest instead and report which descriptor is at fault. Signed-off-by: Arpit Jain --- pkg/imgpkg/imagedesc/image_ref_descriptors.go | 40 +++++++++++++++ .../imagedesc/image_ref_descriptors_test.go | 49 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 pkg/imgpkg/imagedesc/image_ref_descriptors_test.go diff --git a/pkg/imgpkg/imagedesc/image_ref_descriptors.go b/pkg/imgpkg/imagedesc/image_ref_descriptors.go index 10f6a8925..83eeab22d 100644 --- a/pkg/imgpkg/imagedesc/image_ref_descriptors.go +++ b/pkg/imgpkg/imagedesc/image_ref_descriptors.go @@ -51,9 +51,49 @@ func NewImageRefDescriptorsFromBytes(data []byte) (*ImageRefDescriptors, error) return nil, err } + for _, desc := range descs { + switch { + case desc.Image != nil: + if err := validateImageRefs(*desc.Image); err != nil { + return nil, err + } + case desc.ImageIndex != nil: + if err := validateImageIndexRefs(*desc.ImageIndex); err != nil { + return nil, err + } + } + } + return &ImageRefDescriptors{descs: descs}, nil } +// validateImageRefs checks that a descriptor carries the ref that Ref() reads. +// Every descriptor this package writes has exactly one, but nothing stops a +// hand-written or truncated manifest from arriving without any. +func validateImageRefs(desc ImageDescriptor) error { + if len(desc.Refs) == 0 { + return fmt.Errorf("Expected image descriptor %s to have at least one ref", desc.Manifest.Digest) + } + return nil +} + +func validateImageIndexRefs(desc ImageIndexDescriptor) error { + if len(desc.Refs) == 0 { + return fmt.Errorf("Expected image index descriptor %s to have at least one ref", desc.Digest) + } + for _, img := range desc.Images { + if err := validateImageRefs(img); err != nil { + return err + } + } + for _, idx := range desc.Indexes { + if err := validateImageIndexRefs(idx); err != nil { + return err + } + } + return nil +} + func NewImageRefDescriptors(refs []Metadata, registry Registry) (*ImageRefDescriptors, error) { registry = errRegistry{registry} diff --git a/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go b/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go new file mode 100644 index 000000000..7ca8ed382 --- /dev/null +++ b/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package imagedesc_test + +import ( + "strings" + "testing" + + "carvel.dev/imgpkg/pkg/imgpkg/imagedesc" +) + +func TestNewImageRefDescriptorsFromBytes(t *testing.T) { + t.Run("rejects an image descriptor with no refs", func(t *testing.T) { + manifest := `[{"Image":{"Refs":[],"Manifest":{"Digest":"sha256:aaaa"}}}]` + + _, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err == nil { + t.Fatal("expected an error, got none") + } + if !strings.Contains(err.Error(), "at least one ref") { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("rejects an image nested in an index with no refs", func(t *testing.T) { + manifest := `[{"ImageIndex":{"Refs":["registry.example.com/repo@sha256:bbbb"],"Digest":"sha256:bbbb","Images":[{"Refs":[],"Manifest":{"Digest":"sha256:aaaa"}}]}}]` + + _, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err == nil { + t.Fatal("expected an error, got none") + } + if !strings.Contains(err.Error(), "at least one ref") { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("accepts a descriptor with a ref", func(t *testing.T) { + manifest := `[{"Image":{"Refs":["registry.example.com/repo@sha256:aaaa"],"Manifest":{"Digest":"sha256:aaaa"}}}]` + + ids, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err != nil { + t.Fatalf("got an error: %v", err) + } + if got := len(ids.Descriptors()); got != 1 { + t.Errorf("expected 1 descriptor, got %d", got) + } + }) +}