Skip to content

Commit 6ddbc26

Browse files
authored
Merge pull request #711 from projectdiscovery/dwisiswant0/fix/httputil/prevent-memory-corruption-in-ResponseChain
fix(httputil): prevent memory corruption in `ResponseChain`
2 parents e206701 + 99ca5c1 commit 6ddbc26

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

http/respChain.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,9 @@ func (r *ResponseChain) HeadersBytes() []byte {
259259

260260
// HeadersString returns the current response headers as string in the chain.
261261
//
262-
// The returned string is valid only until Close() is called.
263-
// This is a zero-copy operation for performance.
262+
// The returned string is a copy and remains valid even after Close() is called.
264263
func (r *ResponseChain) HeadersString() string {
265-
return conversion.String(r.headers.Bytes())
264+
return r.headers.String()
266265
}
267266

268267
// Body returns the current response body buffer in the chain.
@@ -282,10 +281,9 @@ func (r *ResponseChain) BodyBytes() []byte {
282281

283282
// BodyString returns the current response body as string in the chain.
284283
//
285-
// The returned string is valid only until Close() is called.
286-
// This is a zero-copy operation for performance.
284+
// The returned string is a copy and remains valid even after Close() is called.
287285
func (r *ResponseChain) BodyString() string {
288-
return conversion.String(r.body.Bytes())
286+
return r.body.String()
289287
}
290288

291289
// FullResponse returns a new buffer containing headers+body.
@@ -319,7 +317,6 @@ func (r *ResponseChain) FullResponseBytes() []byte {
319317
// FullResponseString returns the current response as string in the chain.
320318
//
321319
// The returned string is a copy and remains valid even after Close() is called.
322-
// This is a zero-copy operation from the byte slice.
323320
func (r *ResponseChain) FullResponseString() string {
324321
return conversion.String(r.FullResponseBytes())
325322
}

http/respChain_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,3 +1182,48 @@ func TestLimitedBuffer_Pool(t *testing.T) {
11821182
require.Equal(t, len(data), buf.Len())
11831183
require.Equal(t, data, buf.Bytes())
11841184
}
1185+
1186+
func TestResponseChain_StringSafety(t *testing.T) {
1187+
bodyContent := "Original Body Content That Should Be Preserved Yeah Okay LOL"
1188+
headerKey := "X-Safety-Test"
1189+
headerValue := "Original Header Value"
1190+
1191+
resp := &http.Response{
1192+
StatusCode: 200,
1193+
Body: io.NopCloser(strings.NewReader(bodyContent)),
1194+
Header: http.Header{headerKey: []string{headerValue}},
1195+
Proto: "HTTP/1.1",
1196+
ProtoMajor: 1,
1197+
ProtoMinor: 1,
1198+
}
1199+
1200+
rc := NewResponseChain(resp, -1)
1201+
err := rc.Fill()
1202+
require.NoError(t, err)
1203+
1204+
bodyStr := rc.BodyString()
1205+
headersStr := rc.HeadersString()
1206+
1207+
assert.Equal(t, bodyContent, bodyStr)
1208+
assert.Contains(t, headersStr, headerValue)
1209+
1210+
rc.Close()
1211+
1212+
// Now attempt to pollute the pool and overwrite the memory.
1213+
// We get a bunch of buffers and write garbage to them.
1214+
var buffers []*bytes.Buffer
1215+
for i := 0; i < 100; i++ {
1216+
buf := getBuffer()
1217+
buffers = append(buffers, buf)
1218+
1219+
buf.Reset()
1220+
buf.WriteString("ALERTA_GARBAGE_DATA_OVERWRITING_MEMORY_ALERTA_GARBAGE_DATA_OVERWRITING_MEMORY")
1221+
}
1222+
1223+
for _, buf := range buffers {
1224+
putBuffer(buf)
1225+
}
1226+
1227+
assert.Equal(t, bodyContent, bodyStr, "BodyString() content changed after buffer reuse - unsafe memory sharing detected")
1228+
assert.Contains(t, headersStr, headerValue, "HeadersString() content changed after buffer reuse - unsafe memory sharing detected")
1229+
}

0 commit comments

Comments
 (0)