whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF

This commit is contained in:
russell@unturf.com 2026-03-27 22:18:31 -04:00
parent e4ee168b1e
commit 2e4f7807d5
401 changed files with 3914 additions and 114 deletions

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000225
--- a/model/labels/labels_slicelabels.go
+++ b/model/labels/labels_slicelabels.go
@@ -415,14 +415,22 @@ func NewBuilder(base Labels) *Builder {

View file

@ -0,0 +1,114 @@
# prometheus-0002: dependencyMap.dependencies() O(R²×D) in AnalyseRules
**CWE:** CWE-407 (Algorithmic Complexity — Inefficient Algorithmic Complexity)
**Severity:** HIGH
**Component:** `rules/group.go` — `(dependencyMap).dependencies()`
**Commit:** cb33823
## Defect
`dependencyMap` is `map[Rule][]Rule` mapping each rule to its *dependents* (rules that
consume its output). `dependencies(r)` inverts the map by iterating all entries and
calling `slices.Contains(dependents, r)` on each dependents slice.
```go
// rules/group.go:1090-1103
func (m dependencyMap) dependencies(r Rule) []Rule {
if len(m) == 0 {
return []Rule{}
}
var dependencies []Rule
for rule, dependents := range m { // O(R) map scan
if slices.Contains(dependents, r) { // O(D) slice scan each time
dependencies = append(dependencies, rule)
}
}
return dependencies
}
```
`AnalyseRules` (called once per group reload) calls this for every rule:
```go
// rules/manager.go:512-515
for _, r := range rules { // O(R) outer loop
r.SetDependentRules(depMap.dependents(r)) // O(1) map lookup — fine
r.SetDependencyRules(depMap.dependencies(r)) // O(R×D) per call — BAD
}
```
Total: **O(R² × D)** where R = rules in group, D = average dependents per rule.
## Impact
In a rule group with 500 rules and average fan-out D=10, `AnalyseRules` performs
500 × 500 × 10 = **2,500,000** comparisons on every group reload. Rule groups of
this size are common in Prometheus deployments with auto-generated recording rules
from tools like `kube-prometheus`.
## Fix
Build the inverse map (rule → its dependencies) inside `buildDependencyMap` so that
`dependencies(r)` becomes an O(1) lookup.
```go
// Add a second map alongside dependencyMap:
type dependencyMap map[Rule][]Rule // rule → rules that depend on it (dependents)
type dependenciesMap map[Rule][]Rule // rule → rules it depends on (dependencies)
// OR: extend buildDependencyMap to maintain both directions simultaneously.
// The forward relationship is already stored; add the reverse at the same time:
// In buildDependencyMap, alongside:
// dependencies[other] = append(dependencies[other], rule)
// also maintain:
// inverseDeps[rule] = append(inverseDeps[rule], other)
//
// Then dependencies(r) is just:
// return inverseDeps[r] // O(1)
```
Minimal patch to `buildDependencyMap` and `dependencyMap`:
```go
// rules/group.go
// dependencyMap maps a rule to the rules which depend on its output (dependents).
// dependencyMap also carries the inverse: m.inverse maps a rule to its own dependencies.
type dependencyMap struct {
forward map[Rule][]Rule // rule → dependents
inverse map[Rule][]Rule // rule → dependencies
}
func (m dependencyMap) dependents(r Rule) []Rule {
return m.forward[r]
}
func (m dependencyMap) dependencies(r Rule) []Rule {
if m.inverse == nil {
return []Rule{}
}
return m.inverse[r]
}
func buildDependencyMap(rules []Rule) dependencyMap {
dm := dependencyMap{
forward: make(map[Rule][]Rule),
inverse: make(map[Rule][]Rule),
}
// ...existing loop unchanged, but after each:
// dm.forward[other] = append(dm.forward[other], rule)
// also add:
// dm.inverse[rule] = append(dm.inverse[rule], other)
}
```
## Complexity
| Version | AnalyseRules cost | R=500, D=10 ops |
|----------|---------------------------|-----------------|
| Defective | O(R × R × D) | 2,500,000 |
| Patched | O(R) map lookups O(1) each | 500 |
| Speedup | | **5000×** |
At R=100, D=5: defective = 50,000 ops; patched = 100 ops → **500× speedup**.