diff --git a/Makefile b/Makefile index 755b9bdfd62..c1f05b53672 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ TESTFLAGS := LDFLAGS := -w -s GOFLAGS := CGO_ENABLED ?= 0 +GOFIPS140 ?= certified # Rebuild the binary if any of these files change SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum @@ -78,7 +79,7 @@ all: build build: $(BINDIR)/$(BINNAME) $(BINDIR)/$(BINNAME): $(SRC) - CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm # ------------------------------------------------------------------------------ # install @@ -176,13 +177,13 @@ $(GOIMPORTS): .PHONY: build-cross build-cross: LDFLAGS += -extldflags "-static" build-cross: - CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o "_dist/linux-amd64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=amd64 GOOS=darwin go build -o "_dist/darwin-amd64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=arm64 GOOS=darwin go build -o "_dist/darwin-arm64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=amd64 GOOS=windows go build -o "_dist/windows-amd64/$(BINNAME).exe" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build -o "_dist/linux-arm64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=ppc64le GOOS=linux go build -o "_dist/linux-ppc64le/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm - CGO_ENABLED=0 GOARCH=s390x GOOS=linux go build -o "_dist/linux-s390x/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o "_dist/linux-amd64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=amd64 GOOS=darwin go build -o "_dist/darwin-amd64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=arm64 GOOS=darwin go build -o "_dist/darwin-arm64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=amd64 GOOS=windows go build -o "_dist/windows-amd64/$(BINNAME).exe" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build -o "_dist/linux-arm64/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=ppc64le GOOS=linux go build -o "_dist/linux-ppc64le/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm + GOFIPS140=$(GOFIPS140) CGO_ENABLED=0 GOARCH=s390x GOOS=linux go build -o "_dist/linux-s390x/$(BINNAME)" $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm .PHONY: dist dist: diff --git a/go.mod b/go.mod index 01c2c909167..a2a5f5f0cfb 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/client-go v0.36.2 k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.2 - oras.land/oras-go/v2 v2.6.1 + oras.land/oras-go/v2 v2.6.2 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index ce0e63be549..e8bd370fda3 100644 --- a/go.sum +++ b/go.sum @@ -516,8 +516,8 @@ k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= diff --git a/vendor/modules.txt b/vendor/modules.txt index eb39d3daba2..e76e6378e8b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1268,7 +1268,7 @@ k8s.io/utils/internal/third_party/forked/golang/net k8s.io/utils/net k8s.io/utils/ptr k8s.io/utils/trace -# oras.land/oras-go/v2 v2.6.1 +# oras.land/oras-go/v2 v2.6.2 ## explicit; go 1.25.0 oras.land/oras-go/v2 oras.land/oras-go/v2/content diff --git a/vendor/oras.land/oras-go/v2/content/reader.go b/vendor/oras.land/oras-go/v2/content/reader.go index def9ebde825..9bf852403fe 100644 --- a/vendor/oras.land/oras-go/v2/content/reader.go +++ b/vendor/oras.land/oras-go/v2/content/reader.go @@ -16,6 +16,7 @@ limitations under the License. package content import ( + "bytes" "errors" "fmt" "io" @@ -24,11 +25,15 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -// maxDescriptorSize is the upper-bound for descriptor sizes accepted by -// ReadAll. Descriptors sourced from attacker-supplied OCI layouts can carry -// arbitrarily large Size values; without this cap, make([]byte, desc.Size) -// triggers a runtime panic before any allocation occurs. -const maxDescriptorSize = 32 * 1024 * 1024 // 32 MiB +// maxInitialBufferSize bounds the buffer that ReadAll pre-allocates from +// desc.Size before any content is read. desc.Size is attacker-controllable: a +// crafted OCI layout index.json can declare an arbitrarily large Size (e.g. +// 2^62), and make([]byte, desc.Size) on such a value triggers a runtime panic +// ("makeslice: len out of range") before any allocation occurs. ReadAll caps +// the initial allocation at this value and grows the buffer as it reads, so the +// declared size is never trusted for allocation while legitimately large +// content (e.g. plugin or chart layers) is still read in full. +const maxInitialBufferSize = 32 * 1024 * 1024 // 32 MiB var ( // ErrInvalidDescriptorSize is returned by ReadAll() when @@ -125,22 +130,32 @@ func NewVerifyReader(r io.Reader, desc ocispec.Descriptor) *VerifyReader { // The read content is verified against the size and the digest // using a VerifyReader. func ReadAll(r io.Reader, desc ocispec.Descriptor) ([]byte, error) { - if desc.Size < 0 || desc.Size > maxDescriptorSize { + if desc.Size < 0 { return nil, ErrInvalidDescriptorSize } - buf := make([]byte, desc.Size) vr := NewVerifyReader(r, desc) - if n, err := io.ReadFull(vr, buf); err != nil { + + // Do not pre-allocate desc.Size directly: it is attacker-controllable and a + // forged value (e.g. 2^62) would panic make(). Cap the initial allocation + // and let the buffer grow as content is read. The VerifyReader enforces the + // declared size and digest, so a size that does not match the actual content + // still fails verification rather than over-allocating. + initialCap := desc.Size + if initialCap > maxInitialBufferSize { + initialCap = maxInitialBufferSize + } + buf := bytes.NewBuffer(make([]byte, 0, initialCap)) + if _, err := buf.ReadFrom(vr); err != nil { if errors.Is(err, io.ErrUnexpectedEOF) { - return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, n, desc.Digest.String(), err) + return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, buf.Len(), desc.Digest.String(), err) } return nil, fmt.Errorf("read failed: %w", err) } if err := vr.Verify(); err != nil { return nil, err } - return buf, nil + return buf.Bytes(), nil } // ensureEOF ensures the read operation ends with an EOF and no diff --git a/vendor/oras.land/oras-go/v2/errdef/errors.go b/vendor/oras.land/oras-go/v2/errdef/errors.go index 7adb44b173f..fe7859df17f 100644 --- a/vendor/oras.land/oras-go/v2/errdef/errors.go +++ b/vendor/oras.land/oras-go/v2/errdef/errors.go @@ -26,6 +26,7 @@ var ( ErrMissingReference = errors.New("missing reference") ErrNotFound = errors.New("not found") ErrSizeExceedsLimit = errors.New("size exceeds limit") + ErrTooManyPages = errors.New("too many pages") ErrUnsupported = errors.New("unsupported") ErrUnsupportedVersion = errors.New("unsupported version") ) diff --git a/vendor/oras.land/oras-go/v2/registry/remote/repository.go b/vendor/oras.land/oras-go/v2/registry/remote/repository.go index bc649ec2886..2f98f5fd9a9 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/repository.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/repository.go @@ -127,6 +127,18 @@ type Repository struct { // Reference: https://github.com/oras-project/oras-go/issues/841 ReferrerListPageSize int + // TagListMaxPages limits the total number of pages fetched during tag + // listing, bounding server-driven pagination so a malicious or misbehaving + // registry cannot force unbounded requests. + // If zero, tag listing is unlimited. + TagListMaxPages int + + // ReferrerListMaxPages limits the total number of pages fetched during + // referrer listing, bounding server-driven pagination so a malicious or + // misbehaving registry cannot force unbounded requests. + // If zero, referrer listing is unlimited. + ReferrerListMaxPages int + // MaxMetadataBytes specifies a limit on how many response bytes are allowed // in the server's response to the metadata APIs, such as catalog list, tag // list, and referrers list. @@ -205,6 +217,8 @@ func (r *Repository) clone() *Repository { ManifestMediaTypes: slices.Clone(r.ManifestMediaTypes), TagListPageSize: r.TagListPageSize, ReferrerListPageSize: r.ReferrerListPageSize, + TagListMaxPages: r.TagListMaxPages, + ReferrerListMaxPages: r.ReferrerListMaxPages, MaxMetadataBytes: r.MaxMetadataBytes, SkipReferrersGC: r.SkipReferrersGC, HandleWarning: r.HandleWarning, @@ -400,7 +414,10 @@ func (r *Repository) Tags(ctx context.Context, last string, fn func(tags []strin ctx = auth.AppendRepositoryScope(ctx, r.Reference, auth.ActionPull) url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference) var err error - for err == nil { + for page := 0; err == nil; page++ { + if r.TagListMaxPages > 0 && page >= r.TagListMaxPages { + return fmt.Errorf("tag listing exceeded %d pages: %w", r.TagListMaxPages, errdef.ErrTooManyPages) + } url, err = r.tags(ctx, last, fn, url) // clear `last` for subsequent pages last = "" @@ -512,7 +529,10 @@ func (r *Repository) referrersByAPI(ctx context.Context, desc ocispec.Descriptor url := buildReferrersURL(r.PlainHTTP, ref, artifactType) var err error - for err == nil { + for page := 0; err == nil; page++ { + if r.ReferrerListMaxPages > 0 && page >= r.ReferrerListMaxPages { + return fmt.Errorf("referrer listing exceeded %d pages: %w", r.ReferrerListMaxPages, errdef.ErrTooManyPages) + } url, err = r.referrersPageByAPI(ctx, artifactType, fn, url) } if err == errNoLink { diff --git a/vendor/oras.land/oras-go/v2/registry/remote/utils.go b/vendor/oras.land/oras-go/v2/registry/remote/utils.go index 797169f48f7..481193d0e51 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/utils.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/utils.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -55,9 +56,40 @@ func parseLink(resp *http.Response) (string, error) { if err != nil { return "", err } + // The Link header value is controlled by the (potentially malicious) + // registry. Restrict pagination to the same origin as the originating + // request so that a registry cannot redirect pagination to an arbitrary + // host and turn a listing call into a server-side request forgery. + if !isSameOrigin(resp.Request.URL, linkURL) { + return "", fmt.Errorf("invalid next link %q: not the same origin as %q", link, resp.Request.URL) + } return linkURL.String(), nil } +// isSameOrigin reports whether the two URLs share the same origin, that is the +// same scheme, host, and port (with the default port applied for http/https). +func isSameOrigin(a, b *url.URL) bool { + if !strings.EqualFold(a.Scheme, b.Scheme) { + return false + } + return canonicalHostPort(a) == canonicalHostPort(b) +} + +// canonicalHostPort returns the lower-cased "host:port" of u, filling in the +// default port for the http and https schemes when none is present. +func canonicalHostPort(u *url.URL) string { + port := u.Port() + if port == "" { + switch strings.ToLower(u.Scheme) { + case "https": + port = "443" + case "http": + port = "80" + } + } + return strings.ToLower(u.Hostname()) + ":" + port +} + // limitReader returns a Reader that reads from r but stops with EOF after n // bytes. If n is less than or equal to zero, defaultMaxMetadataBytes is used. func limitReader(r io.Reader, n int64) io.Reader {