Skip to content

Commit 8ab0d8f

Browse files
committed
fix(confighttp): check snappy decoded length before read
Assisted-by: ChatGPT 5
1 parent ac5631e commit 8ab0d8f

2 files changed

Lines changed: 48 additions & 5 deletions

File tree

config/confighttp/compression.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"bytes"
1111
"compress/gzip"
1212
"compress/zlib"
13+
"encoding/binary"
1314
"errors"
1415
"fmt"
1516
"io"
@@ -152,19 +153,25 @@ func newSnappyHandler(maxRequestBodySize int64) func(io.ReadCloser) (io.ReadClos
152153
}, nil
153154
}
154155

155-
compressed, err := io.ReadAll(br)
156-
if err != nil {
157-
return nil, err
158-
}
159156
if maxRequestBodySize > 0 {
160-
decodedLen, decErr := snappy.DecodedLen(compressed)
157+
// Peek MaxVarintLen64 bytes so we can read the decoded length
158+
// before reading the full compressed request body.
159+
lenBytes, err := br.Peek(binary.MaxVarintLen64)
160+
if err != nil && !errors.Is(err, io.EOF) {
161+
return nil, err
162+
}
163+
decodedLen, decErr := snappy.DecodedLen(lenBytes)
161164
if decErr != nil {
162165
return nil, decErr
163166
}
164167
if int64(decodedLen) > maxRequestBodySize {
165168
return nil, errors.New("snappy: decoded size exceeds max request body size")
166169
}
167170
}
171+
compressed, err := io.ReadAll(br)
172+
if err != nil {
173+
return nil, err
174+
}
168175
decoded, err := snappy.Decode(nil, compressed)
169176
if err != nil {
170177
return nil, err

config/confighttp/compression_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"compress/gzip"
99
"compress/zlib"
1010
"context"
11+
"encoding/binary"
1112
"errors"
1213
"fmt"
1314
"io"
@@ -961,6 +962,41 @@ func TestSnappyBlockRejectsOversizedDecodedLen(t *testing.T) {
961962
assert.False(t, downstreamCalled, "downstream handler must not run when request is rejected")
962963
}
963964

965+
func TestSnappyBlockRejectsOversizedDecodedLenBeforeCompressedBodyLimit(t *testing.T) {
966+
t.Parallel()
967+
968+
const maxBody = 1024
969+
970+
payload := make([]byte, binary.MaxVarintLen64+maxBody+1)
971+
n := binary.PutUvarint(payload, maxBody+1)
972+
payload = payload[:n+maxBody+1]
973+
require.Greater(t, len(payload), maxBody)
974+
975+
downstreamCalled := false
976+
h := maxRequestBodySizeInterceptor(
977+
httpContentDecompressor(
978+
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
979+
downstreamCalled = true
980+
}),
981+
maxBody,
982+
defaultErrorHandler,
983+
defaultCompressionAlgorithms(),
984+
nil,
985+
),
986+
maxBody,
987+
)
988+
989+
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payload))
990+
req.Header.Set("Content-Encoding", "snappy")
991+
992+
resp := httptest.NewRecorder()
993+
h.ServeHTTP(resp, req)
994+
995+
assert.Equal(t, http.StatusBadRequest, resp.Code)
996+
assert.Contains(t, resp.Body.String(), "decoded size exceeds max request body size")
997+
assert.False(t, downstreamCalled, "downstream handler must not run when request is rejected")
998+
}
999+
9641000
func TestPooledZstdReadCloserReadAfterClose(t *testing.T) {
9651001
h := httpContentDecompressor(
9661002
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)