Denial of Service (crash) via input validation panic in ResourceSlice validation

HIGH
kubernetes/kubernetes
Commit: ad12b979da6a
Affected: <= v1.36.0-beta.0
2026-06-30 15:36 UTC

Description

The commit fixes a potential Denial of Service (crash) in ResourceSlice validation by validating that CapacityRequestPolicyRange.validRange.step, when present, is strictly greater than zero. Previously, a zero or negative step could lead to a runtime panic during validation (or related processing), risking a crash or DoS of the API server. The patch adds a guard to reject non-positive steps with a validation error and accompanies tests for zero and negative step values. This indicates a security-hardening fix rather than a mere dependency bump or refactor.

Proof of Concept

Proof-of-concept (requires a cluster with the DRA ResourceSlice CRD installed and OpenAPI validation enabled): 1) Create a ResourceSlice resource with a device capacity that uses a validRange.step of 0 (or a negative value). The API server should reject the object with a validation error rather than crash. Example YAML (structure corresponds to the ResourceSlice CRD used by the DRA feature; adjust field names if your CRD uses different aliases): apiVersion: resource.k8s.io/v1alpha1 kind: ResourceSlice metadata: name: test-rslice-step0 spec: devices: - name: device-cap1 capacity: cap: value: "1Gi" requestPolicy: default: "10Mi" validRange: min: "1Mi" max: "100Mi" step: "0" # intentionally invalid: must be > 0 # Expected outcome: # The API server should return a 400 Bad Request with a message similar to: # must be greater than zero # and will not crash. This demonstrates the patch handling invalid steps gracefully.

Commit Details

Author: wilmerdooley

Date: 2026-06-13 03:49 UTC

Message:

feat: DRA ResourceSlice validation panics on zero validRange.step Signed-off-by: wilmerdooley <wilmerdooley1@gmail.com>

Triage Assessment

Vulnerability Type: Denial of Service (crash)

Confidence: HIGH

Reasoning:

The commit adds validation to ensure validRange.step is greater than zero and prevents a panic when processing ResourceSlice validation. This reduces risk of crashes or DoS due to malformed input, which is a security-hardening fix. The change explicitly handles zero or negative steps and adds tests for invalid values.

Verification Assessment

Vulnerability Type: Denial of Service (crash) via input validation panic in ResourceSlice validation

Confidence: HIGH

Affected Versions: <= v1.36.0-beta.0

Code Diff

diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 0706817a328f1..8f28bf073d7f0 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -1175,14 +1175,18 @@ func validateRequestPolicyRange(defaultValue apiresource.Quantity, maxCapacity a } } if valueRange.Step != nil { - added := valueRange.Min.DeepCopy() - added.Add(*valueRange.Step) - if added.Cmp(maxCapacity) > 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("step"), valueRange.Step.String(), fmt.Sprintf("one step %s is larger than capacity value: %s", added.String(), maxCapacity.String()))) - } - allErrs = append(allErrs, validateRequestPolicyRangeStep(defaultValue, *valueRange.Min, *valueRange.Step, fldPath.Child("step"))...) - if valueRange.Max != nil { - allErrs = append(allErrs, validateRequestPolicyRangeStep(*valueRange.Max, *valueRange.Min, *valueRange.Step, fldPath.Child("step"))...) + if valueRange.Step.Sign() <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("step"), valueRange.Step.String(), "must be greater than zero")) + } else { + added := valueRange.Min.DeepCopy() + added.Add(*valueRange.Step) + if added.Cmp(maxCapacity) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("step"), valueRange.Step.String(), fmt.Sprintf("one step %s is larger than capacity value: %s", added.String(), maxCapacity.String()))) + } + allErrs = append(allErrs, validateRequestPolicyRangeStep(defaultValue, *valueRange.Min, *valueRange.Step, fldPath.Child("step"))...) + if valueRange.Max != nil { + allErrs = append(allErrs, validateRequestPolicyRangeStep(*valueRange.Max, *valueRange.Min, *valueRange.Step, fldPath.Child("step"))...) + } } } return allErrs diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go index 8e763c0bbe6ac..7570caeb2e204 100644 --- a/pkg/apis/resource/validation/validation_resourceslice_test.go +++ b/pkg/apis/resource/validation/validation_resourceslice_test.go @@ -834,6 +834,56 @@ func TestValidateResourceSlice(t *testing.T) { }(), consumableCapacityFeatureGate: true, }, + "invalid-request-policy-zero-step": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("capacity").Key("cap").Child("requestPolicy").Child("validRange", "step"), "0", "must be greater than zero"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, 2) + slice.Spec.Devices[1].AllowMultipleAllocations = ptr.To(true) + capacity := resourceapi.DeviceCapacity{ + Value: resource.MustParse("1Gi"), + RequestPolicy: &resourceapi.CapacityRequestPolicy{ + Default: ptr.To(resource.MustParse("10Mi")), + ValidRange: &resourceapi.CapacityRequestPolicyRange{ + Min: ptr.To(resource.MustParse("1Mi")), + Max: ptr.To(resource.MustParse("100Mi")), + Step: ptr.To(resource.MustParse("0")), + }, + }, + } + slice.Spec.Devices[1].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{ + "cap": capacity, + } + return slice + }(), + consumableCapacityFeatureGate: true, + }, + "invalid-request-policy-negative-step": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("capacity").Key("cap").Child("requestPolicy").Child("validRange", "step"), "-1Mi", "must be greater than zero"), + }, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, 2) + slice.Spec.Devices[1].AllowMultipleAllocations = ptr.To(true) + capacity := resourceapi.DeviceCapacity{ + Value: resource.MustParse("1Gi"), + RequestPolicy: &resourceapi.CapacityRequestPolicy{ + Default: ptr.To(resource.MustParse("10Mi")), + ValidRange: &resourceapi.CapacityRequestPolicyRange{ + Min: ptr.To(resource.MustParse("1Mi")), + Max: ptr.To(resource.MustParse("100Mi")), + Step: ptr.To(resource.MustParse("-1Mi")), + }, + }, + } + slice.Spec.Devices[1].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{ + "cap": capacity, + } + return slice + }(), + consumableCapacityFeatureGate: true, + }, "invalid-node-selecor-label-value": { wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "nodeSelector", "nodeSelectorTerms").Index(0).Child("matchExpressions").Index(0).Child("values").Index(0), "-1", "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')")}, slice: func() *resourceapi.ResourceSlice {
← Back to Alerts View on GitHub →