diff --git a/k8s-operator/sessionrecording/spdy/frame.go b/k8s-operator/sessionrecording/spdy/frame.go index ae103c7e0..53a993317 100644 --- a/k8s-operator/sessionrecording/spdy/frame.go +++ b/k8s-operator/sessionrecording/spdy/frame.go @@ -222,7 +222,12 @@ func parseHeaders(decompressor io.Reader, log *zap.SugaredLogger) (http.Header, if err != nil { return nil, fmt.Errorf("error determining num headers: %v", err) } - h := make(http.Header, numHeaders) + // numHeaders is attacker controlled, so it must not be used to pre-size the + // map: a small frame can declare ~4 billion headers and make Go reserve + // gigabytes of bucket storage before a single header is read. The map grows + // itself fine, and the io.LimitReader above bounds how many we can actually + // read. + h := make(http.Header) for range numHeaders { name, err := readLenBytes() if err != nil { diff --git a/k8s-operator/sessionrecording/spdy/frame_test.go b/k8s-operator/sessionrecording/spdy/frame_test.go index 252666119..2eed0078e 100644 --- a/k8s-operator/sessionrecording/spdy/frame_test.go +++ b/k8s-operator/sessionrecording/spdy/frame_test.go @@ -261,6 +261,31 @@ func Test_spdyFrame_parseHeaders_decompressionBomb(t *testing.T) { if got.Get("x") != strings.Repeat("A", 16) { t.Fatalf("unexpected header value: got %q", got.Get("x")) } + + // A block declaring a huge number of headers must be rejected without + // pre-sizing a map for that many entries. The declared count is only 4 + // bytes, so it slips under the io.LimitReader cap; the fix is to not feed + // it to make(). Without that fix this does not fail, it OOMs the binary. + countBomb := bytes.NewBuffer(nil) + writeControlFramePayloadBeforeHeaders(t, countBomb, SYN_STREAM, 1) + cw, err := zlib.NewWriterLevelDict(countBomb, zlib.BestCompression, spdyTxtDictionary) + if err != nil { + t.Fatalf("error creating zlib writer: %v", err) + } + if err := binary.Write(cw, binary.BigEndian, uint32(0xFFFFFFFF)); err != nil { + t.Fatal(err) + } + if err := cw.Flush(); err != nil { + t.Fatal(err) + } + if err := cw.Close(); err != nil { + t.Fatal(err) + } + var z3 zlibReader + sf3 := &spdyFrame{Ctrl: true, Type: SYN_STREAM, Payload: countBomb.Bytes()} + if _, err := sf3.parseHeaders(&z3, zl.Sugar()); err == nil { + t.Fatal("parseHeaders accepted a block declaring 0xFFFFFFFF headers") + } } // Test_spdyFrame_ParseRand calls spdyFrame.Parse with randomly generated bytes