Skip to content

Commit 56e1235

Browse files
authored
[pkg/confighttp] Check snappy decoded length before read (#15271)
Follow-up to #15253. No need for another changelog item
1 parent a81b88a commit 56e1235

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"
@@ -153,19 +154,25 @@ func newSnappyHandler(maxRequestBodySize int64) func(io.ReadCloser) (io.ReadClos
153154
}
154155
defer body.Close()
155156

156-
compressed, err := io.ReadAll(br)
157-
if err != nil {
158-
return nil, err
159-
}
160157
if maxRequestBodySize > 0 {
161-
decodedLen, decErr := snappy.DecodedLen(compressed)
158+
// Peek MaxVarintLen64 bytes so we can read the decoded length
159+
// before reading the full compressed request body.
160+
lenBytes, peakErr := br.Peek(binary.MaxVarintLen64)
161+
if peakErr != nil && !errors.Is(peakErr, io.EOF) {
162+
return nil, peakErr
163+
}
164+
decodedLen, decErr := snappy.DecodedLen(lenBytes)
162165
if decErr != nil {
163166
return nil, decErr
164167
}
165168
if int64(decodedLen) > maxRequestBodySize {
166169
return nil, errors.New("snappy: decoded size exceeds max request body size")
167170
}
168171
}
172+
compressed, err := io.ReadAll(br)
173+
if err != nil {
174+
return nil, err
175+
}
169176
decoded, err := snappy.Decode(nil, compressed)
170177
if err != nil {
171178
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 TestSnappyBlockClosesOriginalBody(t *testing.T) {
9651001
t.Parallel()
9661002

0 commit comments

Comments
 (0)