92 lines
3.2 KiB
Markdown
92 lines
3.2 KiB
Markdown
# 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.
|