-
Couldn't load subscription status.
- Fork 3.6k
Client's buffer for non-101 response body issue optimization proposition #1005
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
base: main
Are you sure you want to change the base?
Conversation
Implement a limit on the error response size for WebSocket handshakes. Signed-off-by: Benoit <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A simpler solution is to leave most of the code as is, but to get the size of the error response buffer from new Dialer field MaxErrorBodySize int. If the Dialer. MaxErrorBodySize is zero, then use the original buffer size of 1024.
The Dialer setting allows apps to adjust the value as needed. The 4096 in this PR may not be large enough.
Feel free to ignore everything I wrote as I am not a maintainer.
client.go
Outdated
| // non-nil *http.Response so that callers can handle redirects, authentication, | ||
| // etcetera. The response body may not contain the entire response and does not | ||
| // need to be closed by the application. | ||
| var maxErrorResponseSize = 4096 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location of this variable declaration breaks the documentation (the DialContext documentation is no longer associated with the method). Move the var declaration.
While you are at it, change it to a const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup indeed you're right there !
client.go
Outdated
| n, _ := io.ReadFull(resp.Body, buf) | ||
| resp.Body = io.NopCloser(bytes.NewReader(buf[:n])) | ||
|
|
||
| limReader := io.LimitReader(resp.Body, int64(maxErrorResponseSize)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete the conversion to int64. The conversion is not required if maxErrorResponseSize is changed from a variable to a constant.
client.go
Outdated
|
|
||
| limReader := io.LimitReader(resp.Body, int64(maxErrorResponseSize)) | ||
| buf, err := io.ReadAll(limReader) | ||
| if err != nil && err != io.EOF { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
io.ReadAll never returns io.EOF. Use if err != nil {.
client_server_test.go
Outdated
| if len(p) != maxErrorResponseSize { | ||
| t.Fatalf("body size=%d, want %d", len(p), maxErrorResponseSize) | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eliminate the duplicated test by using table driven test.
for _, t := range []struct{ size, expect int }{{maxErrorResponseSize + 100, maxErrorResponseSize}, {maxErrorResponseSize, maxErrorResponseSize}} {
body := bytes.Repeat([]byte{'a'}, t.size)
...
if len(p) != t.expected {
t.Fatalf("body size=%d, want %d for original body size %d", len(p), t.expected, t.size)
}
}
Well I thought about adding the MaxErrorBodySize as a setting within the Dialer Struct. But we must make sure it can’t read an arbitrarily large / unbounded error body, otherwise a malicious or just poorly configured server could return a huge response body and cause excessive memory usage or even a denial-of-service. That’s why I was thinking a hard cap (or a read timeout) would still be necessary. |
With my proposal, the maximum number of bytes read is neither arbitrary nor unbounded — it’s explicitly limited. The maximum is defined by the application, or defaults to 1024 bytes if not specified.
What specific threat are you referring to here? Are you suggesting a scenario where an attacker has modified the application’s source code?
It’s not the responsibility of the package to prevent an application from misconfiguring itself. Its role is to behave predictably within the constraints defined by the application.
The current code has a read timeout. |
|
Thank you for the feedback, I'll provide a better version by adding an element to the dial struct as you mentioned. With a default value at 0 which define a buffer size of 1024. |
What type of PR is this? (check all applicable)
Description
This PR addresses the buffer size limitation discussed in issue #994 (made by thorrez) by increasing maxErrorResponseSize from 1024 to 4096 bytes when handling non-101 WebSocket upgrade responses.
When a WebSocket upgrade request fails (returns a status code other than 101), the client reads a portion of the error response body to help application debugging. I wanted to increase the buffer size up to 4096 bytes from 1024 bytes. This limit increases allows larger error responses while still protecting against malicious intents (excessively large bodies).
I added 3 cases under the TestRespOnBadHandshake to verify correct behavior :
Related Tickets & Documents
Added/updated tests?
have not been included
Run verifications and test
make verifyis passingmake testis passing