71 lines
2.2 KiB
Markdown
71 lines
2.2 KiB
Markdown
# UNDF: UNDF-2026-000000106
|
||
# helm-0003: checkRequestedRepos / isRepoRequested — O(n×m) nested linear scan in repo update
|
||
|
||
**Severity:** MEDIUM
|
||
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
|
||
**Speedup:** >15x at repos=200, requestedRepos=50
|
||
**Target:** Helm (helm/helm)
|
||
**Files:**
|
||
- `pkg/cmd/repo_update.go:101` — `isRepoRequested(cfg.Name, o.names)` called inside loop over all repos — O(repos × requestedNames)
|
||
- `pkg/cmd/repo_update.go:158-172` — `checkRequestedRepos` nested loop — O(requestedNames × repos)
|
||
|
||
## Description
|
||
|
||
`runUpdate` iterates over every configured repository and calls `isRepoRequested`
|
||
which performs `slices.Contains(requestedRepos, repoName)` — a O(M) linear scan
|
||
per repo. With R repos and M requested names the outer filter is O(R×M).
|
||
|
||
`checkRequestedRepos` (the validity pre-check) is a nested loop: for each
|
||
requested name, it scans all valid repos linearly — another O(M×R) pass.
|
||
|
||
Both are called on every `helm repo update <names...>` invocation. The two
|
||
O(n×m) traversals compound when users manage large repo lists.
|
||
|
||
## Root Cause
|
||
|
||
```go
|
||
// pkg/cmd/repo_update.go:100-102
|
||
for _, cfg := range f.Repositories {
|
||
if updateAllRepos || isRepoRequested(cfg.Name, o.names) { // O(M) per repo
|
||
...
|
||
}
|
||
}
|
||
|
||
// isRepoRequested — O(M) linear scan
|
||
func isRepoRequested(repoName string, requestedRepos []string) bool {
|
||
return slices.Contains(requestedRepos, repoName)
|
||
}
|
||
|
||
// checkRequestedRepos — O(requested × repos) nested loop
|
||
func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) error {
|
||
for _, requestedRepo := range requestedRepos {
|
||
found := false
|
||
for _, repo := range validRepos { // O(R) per requested name
|
||
if requestedRepo == repo.Name { found = true; break }
|
||
}
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
Fix: build a `map[string]struct{}` from repo names once, use it for O(1)
|
||
membership in both functions.
|
||
|
||
## Patch
|
||
|
||
See `helm-0003-repo-update-linear-scan.patch`
|
||
|
||
## Complexity Before
|
||
|
||
`checkRequestedRepos`: **O(M × R)**
|
||
`runUpdate` filter: **O(R × M)**
|
||
|
||
## Complexity After
|
||
|
||
Build set once: **O(R)**, then O(1) per lookup → **O(R + M)** total
|
||
|
||
## Reproduction
|
||
|
||
```
|
||
cd defects/helm/unit && javac -d . *.java && java -ea unit.HelmTest
|
||
```
|