Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3
oras.land/oras-go/v2 v2.6.1
oras.land/oras-go/v2 v2.6.2
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/kustomize/api v0.21.1
sigs.k8s.io/kustomize/kyaml v0.21.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,8 @@ k8s.io/streaming v0.36.2 h1:NSKthPPg9UFSKsRauVJUVGH2Dvn8fhKmY4qrMkw/p98=
k8s.io/streaming v0.36.2/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
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/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
Expand Down
2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,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
Expand Down
24 changes: 24 additions & 0 deletions vendor/oras.land/oras-go/v2/content/file/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ func extractTarDirectory(dirPath, dirName string, r io.Reader, buf []byte, prese
}
filePath := filepath.Join(dirPath, filePathRel)

// resolveRelToBase only performs lexical and per-component Lstat checks,
// which a chain of previously-extracted symlinks can bypass. Re-verify
// containment with symlinks fully resolved before mutating the
// filesystem, matching the check on the pushFile path.
// (GHSA-m37j-52j7-pjw7)
if err := checkSymlinkEscape(dirPath, filePath); err != nil {
return err
}

// Create content
switch header.Typeflag {
case tar.TypeReg:
Expand All @@ -188,6 +197,11 @@ func extractTarDirectory(dirPath, dirName string, r io.Reader, buf []byte, prese
// This is a known limitation and will not be addressed.
var target string
if target, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil {
if !filepath.IsAbs(target) {
// link(2) resolves relative paths against the process CWD, not
// the link file's directory. Resolve explicitly to prevent escape.
target = filepath.Join(filepath.Dir(filePath), target)
}
err = os.Link(target, filePath)
}
case tar.TypeSymlink:
Expand Down Expand Up @@ -276,6 +290,16 @@ func ensureLinkPath(baseAbs, baseRel, link, target string) (string, error) {

// writeFile writes content to the file specified by the `path` parameter.
func writeFile(path string, r io.Reader, perm os.FileMode, buf []byte) (err error) {
// os.OpenFile follows a terminal symlink, so a regular-file entry whose
// path was already created as a symlink by an earlier archive entry would
// be written through that link, landing outside the extraction root
// (GHSA-m37j-52j7-pjw7). Remove any such symlink first so the content is
// written to a regular file at path itself.
if fi, err := os.Lstat(path); err == nil && fi.Mode()&os.ModeSymlink != 0 {
if err := os.Remove(path); err != nil {
return err
}
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
Expand Down
35 changes: 25 additions & 10 deletions vendor/oras.land/oras-go/v2/content/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package content

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions vendor/oras.land/oras-go/v2/errdef/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
24 changes: 22 additions & 2 deletions vendor/oras.land/oras-go/v2/registry/remote/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = ""
Expand Down Expand Up @@ -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 {
Expand Down
32 changes: 32 additions & 0 deletions vendor/oras.land/oras-go/v2/registry/remote/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -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 {
Expand Down
Loading