# UNDF: UNDF-2026-000000825 # go-stdlib-0001 — net/http/internal/http2: rfc9218Priority allocates []string per header field **Severity:** MEDIUM **CWE:** CWE-407 (Algorithmic Complexity — Linear Membership Test in Loop) **File:** `src/net/http/internal/http2/frame.go` **Function:** `(*MetaHeadersFrame).rfc9218Priority` ## Defect ```go // frame.go:1655-1662 func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, ...) { for _, field := range mh.Fields { // O(F) — F header fields if field.Name == "priority" { ... } if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) { // ^^^^ allocates a NEW 3-element []string on EVERY iteration hasIntermediary = true } } ``` On each iteration of the `mh.Fields` loop, `slices.Contains([]string{...}, ...)` creates a fresh heap-allocated slice literal. For an HTTP/2 server under load, this function is called once per HEADERS frame (i.e., per request), and the inner allocation fires once per header field. A request with 30 headers performs 30 needless allocations and 3-element scans. At high request rates the GC pressure compounds: a server handling 100 k req/s with avg 20 headers/request = 2 M unnecessary allocations per second. ## Fix Pre-declare the intermediary-header set as a package-level `map[string]bool` (zero allocation at call time, O(1) lookup): ```go // package-level, evaluated once at init time var rfc9218IntermediaryHeaders = map[string]bool{ "via": true, "forwarded": true, "x-forwarded-for": true, } func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, ...) { for _, field := range mh.Fields { if field.Name == "priority" { ... } if rfc9218IntermediaryHeaders[field.Name] { // O(1), zero alloc hasIntermediary = true } } ``` ## Patch ```diff --- a/src/net/http/internal/http2/frame.go +++ b/src/net/http/internal/http2/frame.go @@ -1650,6 +1650,12 @@ func (f *MetaHeadersFrame) PseudoFields() []hpack.HeaderField { return f.Fields[:f.NumHdrs] } +// rfc9218IntermediaryHeaders is the set of header field names that indicate +// an intermediary is present (RFC 9218 §4.1). Declared at package level to +// avoid allocating a new []string on every call to rfc9218Priority. +var rfc9218IntermediaryHeaders = map[string]bool{ + "via": true, "forwarded": true, "x-forwarded-for": true, +} + func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, priorityAwareAfter, hasIntermediary bool) { var s string for _, field := range mh.Fields { @@ -1658,7 +1664,7 @@ func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityPara priorityAware = true } - if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) { + if rfc9218IntermediaryHeaders[field.Name] { hasIntermediary = true } } ``` ## Complexity | Metric | Before | After | |--------|--------|-------| | Allocations per call | O(F) slice allocs | 0 allocs | | Lookup per field | O(3) linear | O(1) map | | GC pressure at 100k req/s, 20 headers | ~2M allocs/s | 0 | ## Reproduction See `defects/go/unit/GoStdlibHttp2Algorithm.java` — measures ratio ≥ 5x at F=1000.