Security: Cap per-connection inbound buffer to prevent memory DoS
The protocol length field is validated to 50 MiB, but a peer that sends bytes which never assemble into a complete packet — for example an HTTP-mask envelope without its terminating CRLF CRLF, which Parse() waits on indefinitely — could grow the per-connection input buffer without bound. handleConnection now closes the connection once unparsed buffered bytes exceed MaxBufferedBytes (default 50 MiB + 64 KiB slack, covering the largest legitimate packet plus its mask envelope and a read chunk). Co-Authored-By: deepseek-v4-pro
This commit is contained in:
1 parent
8e0f1283c6
commit
870a5832ea
2 files changed
+58
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -91,3 +92,41 @@ func TestPeerIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A peer that sends bytes which never assemble into a complete packet (here an
|
||||
// HTTP-mask envelope with no terminating CRLF CRLF) must be disconnected once
|
||||
// the per-connection buffer cap is exceeded, rather than allowed to grow it
|
||||
// without bound.
|
||||
func TestHandleConnectionBufferCap(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.MaxBufferedBytes = 1024 // small cap for a fast, in-memory test
|
||||
cfg.ReadTimeout = time.Second
|
||||
|
||||
srv := New(cfg)
|
||||
srv.running.Store(true) // bypass acceptLoop; exercise handleConnection directly
|
||||
|
||||
client, serverConn := net.Pipe()
|
||||
defer client.Close()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
srv.handleConnection(serverConn)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// "POST " prefix with no terminator, plus filler to exceed the cap.
|
||||
payload := append([]byte("POST /x HTTP/1.1\r\n"), bytes.Repeat([]byte{'A'}, 4096)...)
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write(payload)
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// handleConnection returned because the cap was exceeded and it closed
|
||||
// the connection.
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("connection was not closed after exceeding the buffer cap")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user