# UNDF: UNDF-2026-000000446
# kubernetes-0007: CWE-407 — Quadratic exit-code scan in pod failure policy matching

## Severity: MEDIUM

## Repository
github.com/kubernetes/kubernetes
Commit: (depth-1 clone, branch main)

## File
`pkg/controller/job/pod_failure_policy.go`

## Defective Lines
```
107:    func getMatchingContainerFromList(containerStatuses []v1.ContainerStatus,
         requirement *batch.PodFailurePolicyOnExitCodesRequirement) *v1.ContainerStatus {
         for _, containerStatus := range containerStatuses {    // O(C) containers
             ...
114:            if isOnExitCodesOperatorMatching(containerStatus.State.Terminated.ExitCode, requirement) {
                 return &containerStatus
             }
         }
     }

123:    func isOnExitCodesOperatorMatching(exitCode int32,
         requirement *batch.PodFailurePolicyOnExitCodesRequirement) bool {
         switch requirement.Operator {
         case batch.PodFailurePolicyOnExitCodesOpIn:
126:            return slices.Contains(requirement.Values, exitCode)  // O(V) linear scan
         case batch.PodFailurePolicyOnExitCodesOpNotIn:
128:            return !slices.Contains(requirement.Values, exitCode) // O(V) linear scan
         }
     }
```

## Call Chain
```
syncJob() → nonIgnoredFailedPodsCount(failedPods) →
    for _, p := range failedPods {                              // O(P) pods
        matchPodFailurePolicy(policy, p) →
            for _, rule := range policy.Rules {                 // O(R) rules
                matchOnExitCodes(podStatus, rule.OnExitCodes) →
                    getMatchingContainerFromList(containerStatuses, req) →
                        for _, cs := range containerStatuses {  // O(C) containers
                            isOnExitCodesOperatorMatching(exitCode, req) →
                                slices.Contains(req.Values, exitCode) // O(V)
```

Also called from `syncJob()` directly for per-pod action selection (line 1384).

## Complexity
O(P × R × C × V) where:
- P = number of failed pods (can reach thousands in large batch jobs)
- R = number of PodFailurePolicyRules (up to 20 by API validation)
- C = containers per pod (typically 1-5)
- V = exit code values per rule (user-configured, typically 1-10)

The dominant factor is P × R: for a large job with 1000 failed pods and 20 rules,
this performs 20,000 rule evaluations each doing a V-length linear scan.
`requirement.Values` is a `[]int32` slice — never pre-indexed.

## Impact
Large batch jobs with `podFailurePolicy` configured experience quadratic reconciliation
cost in the job controller sync loop. The `nonIgnoredFailedPodsCount` call is in the hot
path of every `syncJob` invocation when `PodFailurePolicy` is set.

## Fix
Pre-build a `map[int32]struct{}` from `requirement.Values` once per rule evaluation
instead of calling `slices.Contains` per container per pod.

```go
// Before (defective):
func isOnExitCodesOperatorMatching(exitCode int32,
    requirement *batch.PodFailurePolicyOnExitCodesRequirement) bool {
    switch requirement.Operator {
    case batch.PodFailurePolicyOnExitCodesOpIn:
        return slices.Contains(requirement.Values, exitCode)
    case batch.PodFailurePolicyOnExitCodesOpNotIn:
        return !slices.Contains(requirement.Values, exitCode)
    }
    return false
}

// After (fixed): build set once at rule-match time
func exitCodeSet(values []int32) map[int32]struct{} {
    s := make(map[int32]struct{}, len(values))
    for _, v := range values {
        s[v] = struct{}{}
    }
    return s
}

func isOnExitCodesOperatorMatchingSet(exitCode int32,
    valueSet map[int32]struct{}) bool {
    _, found := valueSet[exitCode]
    return found
}
// Build set once per rule before iterating containers/pods.
```

## References
- CWE-407: Inefficient Algorithmic Complexity
- `pkg/controller/job/pod_failure_policy.go` lines 107-128
- `pkg/controller/job/job_controller.go` lines 1271, 1384, 1744
