You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I do not have a reliable repro case as it depends on a lot of factors ; but I believe the issue is with the documentation more than with the code:
My application does this (conceptually):
var b bytes.Buffer
var client http.Client
for {
b.Reset() // reuse `b` on every iteration
writeDataToBuffer(&b) // write some data to buffer
req, err := http.NewRequest("POST", "http://my-app/path", &b)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err == nil {
resp.Body.Close()
}
}
What did you see happen?
The above code, depending on whether conditions allow to hit a race condition, will sometimes panic when calling http.NewRequest because http.NewRequest invokes b.Bytes()here. The panic happens on this line in src/bytes/buffer.go because b.off is greater than len(b.buf).
This situation happens because client.Do may return before the HTTP transport is done reading from the request body (for example, when sending the request returns an error). This is somewhat clear from reading this comment and the transport implementation with the separate pconn.readLoop and pconn.writeLoop goroutines.
This means our code may attempt to Reset() and write to the bytes.Buffer while the same buffer is still being read from, which is undefined behavior. Sometimes the code will panic, and sometimes the next request will be corrupted.
The request Body, if non-nil, will be closed by the underlying Transport, even on errors. The Body may be closed asynchronously after Do returns.
But technically, this comment shouldn't matter when we pass a bytes.Buffer as request body, because bytes.Buffer doesn't even implement the io.Closer interface. The http library may wrap it in a NopCloser and close that, but it shouldn't impact the calling code.
What did you expect to see?
The panic/corruption seems unavoidable, and I don't think the library has a bug.
But I would expect to see the http.Client.Do comments to be explicit about the ownership of the request body:
Should the request body be considered immutable from the caller perspective (relinquishing ownership) after invoking client.Do?
Could the caller re-assume ownership of the request body when the request is successful?
I would happily send a PR to correct the documentation, but I'm not quite sure what are the intended semantics.
The text was updated successfully, but these errors were encountered:
vikmik
changed the title
net/http: panic due to undocumented request body read
net/http: panic/corruption due to undocumented request body read
Apr 7, 2024
Go version
go version go1.22.1 linux/amd64
Output of
go env
in your module/workspace:What did you do?
I do not have a reliable repro case as it depends on a lot of factors ; but I believe the issue is with the documentation more than with the code:
My application does this (conceptually):
What did you see happen?
The above code, depending on whether conditions allow to hit a race condition, will sometimes panic when calling
http.NewRequest
becausehttp.NewRequest
invokesb.Bytes()
here. The panic happens on this line insrc/bytes/buffer.go
becauseb.off
is greater thanlen(b.buf)
.This situation happens because
client.Do
may return before the HTTP transport is done reading from the request body (for example, when sending the request returns an error). This is somewhat clear from reading this comment and the transport implementation with the separatepconn.readLoop
andpconn.writeLoop
goroutines.This means our code may attempt to
Reset()
and write to thebytes.Buffer
while the same buffer is still being read from, which is undefined behavior. Sometimes the code will panic, and sometimes the next request will be corrupted.To some extent, the documentation fo
http.Client.Do
is somewhat forewarning, as it says:But technically, this comment shouldn't matter when we pass a
bytes.Buffer
as request body, becausebytes.Buffer
doesn't even implement theio.Closer
interface. The http library may wrap it in aNopCloser
and close that, but it shouldn't impact the calling code.What did you expect to see?
The panic/corruption seems unavoidable, and I don't think the library has a bug.
But I would expect to see the
http.Client.Do
comments to be explicit about the ownership of the request body:client.Do
?I would happily send a PR to correct the documentation, but I'm not quite sure what are the intended semantics.
The text was updated successfully, but these errors were encountered: