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

net/http: panic/corruption due to undocumented request body read #66714

Closed
vikmik opened this issue Apr 7, 2024 · 1 comment
Closed

net/http: panic/corruption due to undocumented request body read #66714

vikmik opened this issue Apr 7, 2024 · 1 comment

Comments

@vikmik
Copy link
Contributor

vikmik commented Apr 7, 2024

Go version

go version go1.22.1 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/vic/.cache/go-build'
GOENV='/home/vic/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/vic/go/pkg/mod'
GONOPROXY='github.com/elastic/*'
GONOSUMDB='github.com/elastic/*'
GOOS='linux'
GOPATH='/home/vic/go'
GOPRIVATE='github.com/elastic/*'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/vic/.local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/vic/.local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.1'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2212814203=/tmp/go-build -gno-record-gcc-switches'

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):

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.

To some extent, the documentation fo http.Client.Do is somewhat forewarning, as it says:

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.

@vikmik 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
@seankhliao
Copy link
Member

Duplicate of #51907

@seankhliao seankhliao marked this as a duplicate of #51907 Apr 8, 2024
@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale Apr 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants