# UNDF: UNDF-2026-000000452
# linkerd2-0002: CWE-407 — Quadratic opaque port lookup during pod injection

## Severity: MEDIUM

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

## File
`pkg/inject/inject.go`

## Defective Lines
```
816:    defaultPorts := strings.Split(conf.GetValues().Proxy.OpaquePorts, ",")  // []string slice

855:    func (conf *ResourceConfig) FilterPodOpaquePorts(defaultPorts []string) []string {
856:        var filteredPorts []string
857:        for _, c := range append(conf.pod.spec.InitContainers, conf.pod.spec.Containers...) { // O(C)
858:            for _, p := range c.Ports {                                                         // O(P)
859:                port := strconv.Itoa(int(p.ContainerPort))
860:                if util.ContainsString(port, defaultPorts) {                                    // O(D)
861:                    filteredPorts = append(filteredPorts, port)
862:                }
863:            }
864:        }
865:        return filteredPorts
866:    }

826:    for _, p := range service.Spec.Ports {                              // O(SP) service ports
834:        if util.ContainsString(port, defaultPorts) {                    // O(D) scan
837:        } else if util.ContainsString(strconv.Itoa(int(p.TargetPort.IntVal)), defaultPorts) { // O(D)
```

## ContainsString Implementation
```go
// pkg/util/parsing.go:77
func ContainsString(str string, collection []string) bool {
    for _, s := range collection {   // O(D) linear scan
        if s == str { return true }
    }
    return false
}
```

## Call Chain
```
Admission webhook per pod/service creation/update →
    CreateAnnotationPatch() [inject.go line ~815] →
        FilterPodOpaquePorts(defaultPorts) →
            for each container × port: ContainsString(port, defaultPorts)  // O(C×P×D)

        for each service port: ContainsString(port, defaultPorts)          // O(SP×D)
```

## Complexity
O(C × P × D) where:
- C = number of containers + init containers per pod
- P = number of exposed ports per container
- D = length of defaultPorts list (comma-separated string, user-configured)

`defaultPorts` defaults to ~25 well-known opaque ports (3306, 5432, 6379, etc.)
but can be extended by operators. It is parsed as `[]string` and scanned linearly
on every port check. The same `defaultPorts` slice is scanned multiple times per
pod without pre-indexing.

## Impact
Every pod and service injection via the linkerd-proxy-injector webhook calls this
path. In clusters with high churn (rolling deployments, HPA scaling), this webhook
is hot. Pods with many containers and ports (e.g., sidecar-heavy microservices)
cause O(C×P×D) work per admission request.

## Fix
Pre-build a `map[string]struct{}` from `defaultPorts` once before the loop.

```go
// Before (defective):
func (conf *ResourceConfig) FilterPodOpaquePorts(defaultPorts []string) []string {
    var filteredPorts []string
    for _, c := range ... {
        for _, p := range c.Ports {
            port := strconv.Itoa(int(p.ContainerPort))
            if util.ContainsString(port, defaultPorts) {   // O(D) per port
                filteredPorts = append(filteredPorts, port)
            }
        }
    }
    return filteredPorts
}

// After (fixed):
func (conf *ResourceConfig) FilterPodOpaquePorts(defaultPorts []string) []string {
    defaultSet := make(map[string]struct{}, len(defaultPorts))
    for _, p := range defaultPorts { defaultSet[p] = struct{}{} }

    var filteredPorts []string
    for _, c := range ... {
        for _, p := range c.Ports {
            port := strconv.Itoa(int(p.ContainerPort))
            if _, ok := defaultSet[port]; ok {              // O(1)
                filteredPorts = append(filteredPorts, port)
            }
        }
    }
    return filteredPorts
}
```

The same fix applies to the service port checks at lines 834 and 837 —
build the set once before the `for _, p := range service.Spec.Ports` loop.

## References
- CWE-407: Inefficient Algorithmic Complexity
- `pkg/inject/inject.go` lines 816-865
- `pkg/util/parsing.go` lines 76-82 (ContainsString)
