GHSA-2hw4-c9m4-f2fj: Medium severity go/knative.dev/pkg vulnerability
⚠️ Update (verification, 2026-05-19): C-2 confirmed end-to-end — 4.5× amplification observed (worse than the advisory's conservative 2× claim)
Built a Go PoC (cmd/poc-c2/main.go) that runs the exact 8-line vulnerable snippet from webhook/admission.go:109-117 verbatim under the same http.HandlerFunc shape (the full production admissionHandler at :85 requires Webhook + AdmissionController + an informer-synced channel + controller-runtime / client-go scaffolding — the bug is the missing http.MaxBytesReader, nothing else in the handler). Bound to 127.0.0.1:18098.
PoC client streamed 2 GiB of a bytes over a raw TCP socket. runtime.MemStats.HeapAlloc sampler:
baseline: HeapAlloc = 0.32 MiB peak: HeapAlloc = 9 217.38 MiB = 9.00 GiB ratio: 4.50× the body size
The advisory predicted ~2× from io.TeeReader doubling the body once in bodyBuffer. The observed 4.5× ratio comes from the interaction of two amplifiers: bytes.Buffer.ReadFrom (used by TeeReader-fed bodyBuffer) doubles its backing slice on every grow, so peak occupancy during a realloc is old + new = 3× the live data — and json.Decoder holds its own working buffer in parallel. The advisory's 2× floor is comfortably exceeded.
Downstream impact reminder: this library backs every Knative serving / eventing controller, every Tekton Triggers / Pipelines webhook, KEDA, Sigstore policy-controller, Kpack, and many more. A 2 GiB body to any of those projects' admission webhook ports OOMs the controller at 4.5× the body — i.e., even a 128 MiB request OOMs a 512 MiB-limited Pod.
---
Summary
knative.dev/pkg's admission webhook handler reads the incoming HTTP request body via json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer)) with no http.MaxBytesReader wrapping the body. Because every consumer of knative.dev/pkg/webhook inherits this handler (Knative serving / eventing, Tekton Pipelines, KEDA, Sigstore policy-controller, and others), a single oversized POST to any of those projects' admission webhook ports drives the controller pod into memory exhaustion and OOM-kill. Worse, the io.TeeReader design keeps a verbatim copy of the body in bodyBuffer while the decoder also materializes the parsed struct — so the peak memory usage is roughly twice the body size.
This is the same shape as CVE-2026-24514 (ingress-nginx) and GHSA-9539-c3xr-w9vm (Kyverno admission body OOM) filed today against the same antipattern.
Affected versions
- knative.dev/pkg — main HEAD on github.com/knative/pkg (verified 2026-05-18 against webhook/admission.go sha 1ad5a1ce0ee7043081cb50fd1acc6d36a9126844). - Every release that ships the current webhook/admission.go handler.
Vulnerable code
webhook/admission.go:109-117:
go review := admissionv1.AdmissionReview{} var bodyBuffer bytes.Buffer if err := json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer)).Decode(&review); err != nil { http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest) return }
There is no http.MaxBytesReader around r.Body. The standard library's json.NewDecoder will read until EOF or the matching closing }. The io.TeeReader simultaneously copies every byte read from r.Body into bodyBuffer, which the rest of the handler uses for the response trailer / observability.
Downstream impact (the reason this matters)
knative.dev/pkg/webhook is the default admission-webhook scaffolding for the entire Knative + CNCF webhook ecosystem:
| Consumer | Repo | Webhook surface | |---|---|---| | Knative serving | knative/serving | resource validation / defaulting | | Knative eventing | knative/eventing | resource validation / defaulting | | Tekton Pipelines | tektoncd/pipeline | Pipeline / Task / TaskRun validation | | Tekton Triggers | tektoncd/triggers | EventListener / Trigger validation | | KEDA | kedacore/keda | ScaledObject / ScaledJob validation | | Sigstore policy-controller | sigstore/policy-controller | ClusterImagePolicy mutating + validating | | Kpack | buildpacks-community/kpack | Image / Builder validation | | ... | | |
Every one of these consumers ships the same io.ReadAll-equivalent body read with no upper bound. A pod-network-reachable attacker can OOM-kill any of them with a single large POST. failurePolicy=Fail admission webhooks (the secure-default) then take the namespace's or cluster's admission flow offline until the controller restarts.
Impact
- Class: CWE-770 unbounded allocation. Reachable from any pod-network client. - Severity per consumer: depends on how each consumer configures failurePolicy. For projects with failurePolicy=Fail (e.g., Sigstore policy-controller's image-policy enforcement), the consequence is cluster-wide admission outage. For failurePolicy=Ignore, the consequence is "this project's webhook is offline" but the API server continues admitting requests. - Severity: matches the broader CVE-2026-24514 / GHSA-9539-c3xr-w9vm. Single-packet DoS on the library; each downstream consumer inherits the bug.
Reproduction
The exact same PoC built against Kyverno's admission handler reproduces this:
python import socket total = 2 << 30 # 2 GiB s = socket.createconnection(('<knative-or-tekton-webhook-host>', 8443)) s.sendall(b'POST /<admission-path> HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n' % total) chunk = b'a' (1 << 20) for in range(total >> 20): s.sendall(chunk)
Observed against the Kyverno admission handler (same antipattern):
baseline: HeapAlloc=7 MiB Sys=22 MiB after read: HeapAlloc=9500 MiB Sys=9548 MiB (4 GiB body → 9.5 GiB peak; io.TeeReader doubles)
For the knative/pkg version, the io.TeeReader doubles the resident memory (one copy in the decoder's accumulating slice, one in bodyBuffer). A kyverno-admission-controller-equivalent Pod with a 256 MiB memory limit OOM-kills with ~128 MiB of body.
Suggested fix
Wrap the request body with http.MaxBytesReader (and add a http.StatusRequestEntityTooLarge branch on the MaxBytesError path):
go const maxAdmissionRequestSize = 8 << 20 // 8 MiB — controller-runtime's default; large enough for Kubernetes' own admission objects r.Body = http.MaxBytesReader(w, r.Body, maxAdmissionRequestSize) var bodyBuffer bytes.Buffer review := admissionv1.AdmissionReview{} if err := json.NewDecoder(io.TeeReader(r.Body, &bodyBuffer)).Decode(&review); err != nil { var mbe http.MaxBytesError if errors.As(err, &mbe) { http.Error(w, "admission body exceeds maximum size", http.StatusRequestEntityTooLarge) return } http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest) return }
This propagates a fix to every downstream consumer with no API change, and the cap can be made configurable per-server via a new field on the webhook.Options struct.
Disclosure
Reporting privately under your security policy. No public disclosure on my side until you have a fix or coordinated timeline. Encourage maintainers to coordinate with downstream consumers (Knative, Tekton, KEDA, Sigstore) on simultaneous release.
Reporter
tonghuaroot (tonghuaroot@gmail.com)
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/knative.dev/pkgto a version that resolves this vulnerability.Fixed in 0.0.0-20260531000007-52dbd5ece63f - Configuration
Define maxAdmissionRequestSize as 8 << 20 (8 MiB), wrap r.Body with http.MaxBytesReader(w, r.Body, maxAdmissionRequestSize), and return http.StatusRequestEntityTooLarge when the read fails with an http.MaxBytesError.
knative.dev/pkg webhook admission handler maxAdmissionRequestSize = 8 MiB
Event History
Frequently Asked Questions
What level of access does an attacker need to trigger the issue?
The CVSS vector indicates network reachability, low privileges, and no user interaction requirement. The attacker must be able to send requests to the affected handler.
What resource impact was observed during verification?
In the reported Go proof of concept, streaming a 2 GiB request body caused HeapAlloc to peak at 9.00 GiB from a 0.32 MiB baseline. This was a 4.5× amplification of the request-body size, exceeding the advisory's conservative 2× expectation.
How can I check whether the vulnerable request-handling pattern is present?
Review the admission request-body handling in webhook/admission.go for a missing http.MaxBytesReader limit. The verification identifies the missing limit as the relevant flaw, rather than the surrounding webhook or controller scaffolding.