Skip to content

Commit aa7e2cc

Browse files
neildgopherbot
authored andcommitted
[internal-branch.go1.19-vendor] http2: limit canonical header cache by bytes, not entries
The canonical header cache is a per-connection cache mapping header keys to their canonicalized form. (For example, "foo-bar" => "Foo-Bar"). We limit the number of entries in the cache to prevent an attacker from consuming unbounded amounts of memory by sending many unique keys, but a small number of very large keys can still consume an unreasonable amount of memory. Track the amount of memory consumed by the cache and limit it based on memory rather than number of entries. Thanks to Josselin Costanzi for reporting this issue. For golang/go#56350 For golang/go#57009 Fixes CVE-2022-41717 Change-Id: Ief3c141001524fd3776958ecc8556c724427f063 Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1619953 Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Julie Qiu <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1662693 Reviewed-by: Tatiana Bradley <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/net/+/455736 Reviewed-by: Jenny Rakoczy <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Dmitri Shuralyov <[email protected]>
1 parent d52c520 commit aa7e2cc

File tree

2 files changed

+37
-7
lines changed

2 files changed

+37
-7
lines changed

http2/server.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ type serverConn struct {
528528
headerTableSize uint32
529529
peerMaxHeaderListSize uint32 // zero means unknown (default)
530530
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
531+
canonHeaderKeysSize int // canonHeader keys size in bytes
531532
writingFrame bool // started writing a frame (on serve goroutine or separate)
532533
writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh
533534
needsFrameFlush bool // last frame write wasn't a flush
@@ -704,6 +705,13 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
704705
}
705706
}
706707

708+
// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size
709+
// of the entries in the canonHeader cache.
710+
// This should be larger than the size of unique, uncommon header keys likely to
711+
// be sent by the peer, while not so high as to permit unreasonable memory usage
712+
// if the peer sends an unbounded number of unique header keys.
713+
const maxCachedCanonicalHeadersKeysSize = 2048
714+
707715
func (sc *serverConn) canonicalHeader(v string) string {
708716
sc.serveG.check()
709717
buildCommonHeaderMapsOnce()
@@ -719,14 +727,10 @@ func (sc *serverConn) canonicalHeader(v string) string {
719727
sc.canonHeader = make(map[string]string)
720728
}
721729
cv = http.CanonicalHeaderKey(v)
722-
// maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
723-
// entries in the canonHeader cache. This should be larger than the number
724-
// of unique, uncommon header keys likely to be sent by the peer, while not
725-
// so high as to permit unreasonable memory usage if the peer sends an unbounded
726-
// number of unique header keys.
727-
const maxCachedCanonicalHeaders = 32
728-
if len(sc.canonHeader) < maxCachedCanonicalHeaders {
730+
size := 100 + len(v)*2 // 100 bytes of map overhead + key + value
731+
if sc.canonHeaderKeysSize+size <= maxCachedCanonicalHeadersKeysSize {
729732
sc.canonHeader[v] = cv
733+
sc.canonHeaderKeysSize += size
730734
}
731735
return cv
732736
}

http2/server_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4489,3 +4489,29 @@ func TestProtocolErrorAfterGoAway(t *testing.T) {
44894489
}
44904490
}
44914491
}
4492+
4493+
// TestCanonicalHeaderCacheGrowth verifies that the canonical header cache
4494+
// size is capped to a reasonable level.
4495+
func TestCanonicalHeaderCacheGrowth(t *testing.T) {
4496+
for _, size := range []int{1, (1 << 20) - 10} {
4497+
base := strings.Repeat("X", size)
4498+
sc := &serverConn{
4499+
serveG: newGoroutineLock(),
4500+
}
4501+
const count = 1000
4502+
for i := 0; i < count; i++ {
4503+
h := fmt.Sprintf("%v-%v", base, i)
4504+
c := sc.canonicalHeader(h)
4505+
if len(h) != len(c) {
4506+
t.Errorf("sc.canonicalHeader(%q) = %q, want same length", h, c)
4507+
}
4508+
}
4509+
total := 0
4510+
for k, v := range sc.canonHeader {
4511+
total += len(k) + len(v) + 100
4512+
}
4513+
if total > maxCachedCanonicalHeadersKeysSize {
4514+
t.Errorf("after adding %v ~%v-byte headers, canonHeader cache is ~%v bytes, want <%v", count, size, total, maxCachedCanonicalHeadersKeysSize)
4515+
}
4516+
}
4517+
}

0 commit comments

Comments
 (0)