Skip to content

Commit ff26614

Browse files
authored
[confighttp] Close block snappy request body (#15262)
Close the original HTTP request body after confighttp eagerly reads block-format snappy payloads, including oversized reject paths, so resources are released instead of being hidden behind an in-memory NopCloser.
1 parent ac5631e commit ff26614

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
7+
component: pkg/confighttp
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Close the original request body after reading block-format `Content-Encoding: snappy` requests."
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [15262]
14+
15+
# Optional: The change log or logs in which this entry should be included.
16+
# e.g. '[user]' or '[user, api]'
17+
# Include 'user' if the change is relevant to end users.
18+
# Include 'api' if there is a change to a library API.
19+
# Default: '[user]'
20+
change_logs: [user]

config/confighttp/compression.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ func newSnappyHandler(maxRequestBodySize int64) func(io.ReadCloser) (io.ReadClos
151151
orig: body,
152152
}, nil
153153
}
154+
defer body.Close()
154155

155156
compressed, err := io.ReadAll(br)
156157
if err != nil {

config/confighttp/compression_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,46 @@ func TestSnappyBlockRejectsOversizedDecodedLen(t *testing.T) {
961961
assert.False(t, downstreamCalled, "downstream handler must not run when request is rejected")
962962
}
963963

964+
func TestSnappyBlockClosesOriginalBody(t *testing.T) {
965+
t.Parallel()
966+
967+
for _, tc := range []struct {
968+
name string
969+
maxBody int64
970+
payload []byte
971+
wantError string
972+
}{
973+
{
974+
name: "valid",
975+
maxBody: 1024,
976+
payload: snappy.Encode(nil, []byte("request body")),
977+
},
978+
{
979+
name: "oversized",
980+
maxBody: 1024,
981+
payload: snappy.Encode(nil, make([]byte, 8*1024)),
982+
wantError: "snappy: decoded size exceeds max request body size",
983+
},
984+
} {
985+
t.Run(tc.name, func(t *testing.T) {
986+
t.Parallel()
987+
988+
body := &closeTrackingReadCloser{Reader: bytes.NewReader(tc.payload)}
989+
newBody, err := newSnappyHandler(tc.maxBody)(body)
990+
991+
assert.True(t, body.closed)
992+
if tc.wantError != "" {
993+
require.EqualError(t, err, tc.wantError)
994+
assert.Nil(t, newBody)
995+
return
996+
}
997+
require.NoError(t, err)
998+
require.NotNil(t, newBody)
999+
require.NoError(t, newBody.Close())
1000+
})
1001+
}
1002+
}
1003+
9641004
func TestPooledZstdReadCloserReadAfterClose(t *testing.T) {
9651005
h := httpContentDecompressor(
9661006
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -993,6 +1033,16 @@ func TestPooledZstdReadCloserReadAfterClose(t *testing.T) {
9931033
assert.Empty(t, resp.Body.String(), "Must match the returned string")
9941034
}
9951035

1036+
type closeTrackingReadCloser struct {
1037+
io.Reader
1038+
closed bool
1039+
}
1040+
1041+
func (ctrc *closeTrackingReadCloser) Close() error {
1042+
ctrc.closed = true
1043+
return nil
1044+
}
1045+
9961046
func compressGzip(tb testing.TB, body []byte) *bytes.Buffer {
9971047
var buf bytes.Buffer
9981048
gw, _ := gzip.NewWriterLevel(&buf, gzip.DefaultCompression)

0 commit comments

Comments
 (0)