Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pgproto3/backend): add a SetMaxBodyLen to limit the max body length for the receive #1839

Merged
merged 1 commit into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pgproto3/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Backend struct {
terminate Terminate

bodyLen int
maxBodyLen int // maxBodyLen is the maximum length of a message body in octets. If a message body exceeds this length, Receive will return an error.
msgType byte
partialMsg bool
authType uint32
Expand Down Expand Up @@ -158,6 +159,9 @@ func (b *Backend) Receive() (FrontendMessage, error) {

b.msgType = header[0]
b.bodyLen = int(binary.BigEndian.Uint32(header[1:])) - 4
if b.maxBodyLen > 0 && b.bodyLen > b.maxBodyLen {
return nil, &ExceededMaxBodyLenErr{b.maxBodyLen, b.bodyLen}
}
b.partialMsg = true
}

Expand Down Expand Up @@ -260,3 +264,12 @@ func (b *Backend) SetAuthType(authType uint32) error {

return nil
}

// SetMaxBodyLen sets the maximum length of a message body in octets. If a message body exceeds this length, Receive will return
// an error. This is useful for protecting against malicious clients that send large messages with the intent of
// causing memory exhaustion.
// The default value is 0.
// If maxBodyLen is 0, then no maximum is enforced.
func (b *Backend) SetMaxBodyLen(maxBodyLen int) {
b.maxBodyLen = maxBodyLen
}
18 changes: 18 additions & 0 deletions pgproto3/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,21 @@ func TestStartupMessage(t *testing.T) {
}
})
}

func TestBackendReceiveExceededMaxBodyLen(t *testing.T) {
t.Parallel()

server := &interruptReader{}
server.push([]byte{'Q', 0, 0, 10, 10})

backend := pgproto3.NewBackend(server, nil)

// Set max body len to 5
backend.SetMaxBodyLen(5)

// Receive regular msg
msg, err := backend.Receive()
assert.Nil(t, msg)
var invalidBodyLenErr *pgproto3.ExceededMaxBodyLenErr
assert.ErrorAs(t, err, &invalidBodyLenErr)
}
9 changes: 9 additions & 0 deletions pgproto3/pgproto3.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ func (e *writeError) Unwrap() error {
return e.err
}

type ExceededMaxBodyLenErr struct {
maxExpectedBodyLen int
actualBodyLen int
}

func (e *ExceededMaxBodyLenErr) Error() string {
return fmt.Sprintf("invalid body length: expected at most %d, but got %d", e.maxExpectedBodyLen, e.actualBodyLen)
}

// getValueFromJSON gets the value from a protocol message representation in JSON.
func getValueFromJSON(v map[string]string) ([]byte, error) {
if v == nil {
Expand Down