Summary
The Kubernetes admission webhook handler reads the entire request body using io.ReadAll(r.Body) without any size limit. Any client that can reach the webhook port within the cluster can send a multi-GB payload, causing the skipper process to exhaust memory and be OOM-killed. This disrupts all Kubernetes admission control, potentially blocking all pod creation and updates.
Vulnerable Code
// dataclients/kubernetes/admission/admission.go:76
body, err := io.ReadAll(r.Body) // <-- NO SIZE LIMIT
if err != nil {
log.Errorf("Failed to read request: %v", err)
w.WriteHeader(http.StatusInternalServerError)
invalidRequests.WithLabelValues(admitterName).Inc()
return
}
var review admissionReview
err = json.Unmarshal(body, &review)
For comparison, the OPA filter has a body size limit:
// filters/openpolicyagent/openpolicyagent.go:68-70
const DefaultMaxRequestBodySize = 1 << 20 // 1MB
// OPA uses a bufferedBodyReader with size limits
Attack Path
- Attacker identifies the admission webhook endpoint (default:
:9443/admission or configured path)
- Attacker sends:
POST /admission HTTP/1.1, Content-Type: application/json with a multi-GB request body
io.ReadAll(r.Body) allocates unbounded memory for the entire body
- Skipper process is OOM-killed by the Kubernetes kubelet
Permission Boundary Analysis
- Attacker: Any client with network access to the admission webhook port within the Kubernetes cluster
- Boundary crossed: Memory safety — unbounded allocation from attacker-controlled input
- Preconditions: Admission webhook endpoint must be network-reachable (default Kubernetes deployment exposes it within cluster network)
- Comparison: OPA filter has
DefaultMaxRequestBodySize (1MB) and semaphore-based memory limit; admission handler has neither
Evidence
| File |
Lines |
Description |
dataclients/kubernetes/admission/admission.go |
76 |
io.ReadAll(r.Body) without size limit |
filters/openpolicyagent/openpolicyagent.go |
68-70 |
OPA filter has DefaultMaxRequestBodySize = 1MB |
filters/openpolicyagent/openpolicyagent.go |
1333-1336 |
OPA uses bufferedBodyReader with size limits |
Tests
dataclients/kubernetes/admission/admission_test.go exists but does not test body size limits
Impact
The admission webhook handler reads the entire request body using io.ReadAll(r.Body) without a size limit. An attacker with in-cluster network access and a valid Kubernetes client certificate can send a multi-GB payload to the webhook endpoint, causing the skipper process to exhaust memory and be OOM-killed. This disrupts admission control for Ingress and RouteGroup resources until the process is automatically restarted by the kubelet.
Scope of impact: Ingress and RouteGroup admission only — not pod creation or other admission controllers.
Recovery: Kubernetes automatically restarts the OOM-killed process, limiting downtime.
Prerequisites: (1) In-cluster network access to the webhook port, (2) valid Kubernetes client certificate.
Mitigation
- Add
http.MaxBytesReader or equivalent body size limit before io.ReadAll
- Follow the OPA filter pattern: define
DefaultMaxRequestBodySize and use a buffered reader with size limits
- Add a configurable
--admission-max-body-size flag
References
Summary
The Kubernetes admission webhook handler reads the entire request body using
io.ReadAll(r.Body)without any size limit. Any client that can reach the webhook port within the cluster can send a multi-GB payload, causing the skipper process to exhaust memory and be OOM-killed. This disrupts all Kubernetes admission control, potentially blocking all pod creation and updates.Vulnerable Code
For comparison, the OPA filter has a body size limit:
Attack Path
:9443/admissionor configured path)POST /admission HTTP/1.1, Content-Type: application/jsonwith a multi-GB request bodyio.ReadAll(r.Body)allocates unbounded memory for the entire bodyPermission Boundary Analysis
DefaultMaxRequestBodySize(1MB) and semaphore-based memory limit; admission handler has neitherEvidence
dataclients/kubernetes/admission/admission.goio.ReadAll(r.Body)without size limitfilters/openpolicyagent/openpolicyagent.goDefaultMaxRequestBodySize= 1MBfilters/openpolicyagent/openpolicyagent.gobufferedBodyReaderwith size limitsTests
dataclients/kubernetes/admission/admission_test.goexists but does not test body size limitsImpact
The admission webhook handler reads the entire request body using io.ReadAll(r.Body) without a size limit. An attacker with in-cluster network access and a valid Kubernetes client certificate can send a multi-GB payload to the webhook endpoint, causing the skipper process to exhaust memory and be OOM-killed. This disrupts admission control for Ingress and RouteGroup resources until the process is automatically restarted by the kubelet.
Scope of impact: Ingress and RouteGroup admission only — not pod creation or other admission controllers.
Recovery: Kubernetes automatically restarts the OOM-killed process, limiting downtime.
Prerequisites: (1) In-cluster network access to the webhook port, (2) valid Kubernetes client certificate.
Mitigation
http.MaxBytesReaderor equivalent body size limit beforeio.ReadAllDefaultMaxRequestBodySizeand use a buffered reader with size limits--admission-max-body-sizeflagReferences