From 1db4be0c32267b7a8a27998089ce96440bed492e Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 11 Oct 2018 01:37:39 +0900 Subject: [PATCH] client: use io.LimitedReader for reading HTTP error client.checkResponseErr() was hanging and consuming infinite memory when the serverResp.Body io.Reader returns infinite stream. This commit prohibits reading more than 1MiB. Signed-off-by: Akihiro Suda --- client/request.go | 10 +++++++++- client/request_test.go | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/client/request.go b/client/request.go index a19d62aa52..9b8639a1e5 100644 --- a/client/request.go +++ b/client/request.go @@ -195,10 +195,18 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error { return nil } - body, err := ioutil.ReadAll(serverResp.body) + bodyMax := 1 * 1024 * 1024 // 1 MiB + bodyR := &io.LimitedReader{ + R: serverResp.body, + N: int64(bodyMax), + } + body, err := ioutil.ReadAll(bodyR) if err != nil { return err } + if bodyR.N == 0 { + return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), bodyMax, serverResp.reqURL) + } if len(body) == 0 { return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), serverResp.reqURL) } diff --git a/client/request_test.go b/client/request_test.go index fda4d88aa1..2847a9a4a3 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -5,12 +5,14 @@ import ( "context" "fmt" "io/ioutil" + "math/rand" "net/http" "strings" "testing" "github.com/docker/docker/api/types" "gotest.tools/assert" + is "gotest.tools/assert/cmp" ) // TestSetHostHeader should set fake host for local communications, set real host @@ -87,3 +89,18 @@ func TestPlainTextError(t *testing.T) { t.Fatalf("expected a Server Error, got %v", err) } } + +func TestInfiniteError(t *testing.T) { + infinitR := rand.New(rand.NewSource(42)) + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + resp := &http.Response{StatusCode: http.StatusInternalServerError} + resp.Header = http.Header{} + resp.Body = ioutil.NopCloser(infinitR) + return resp, nil + }), + } + + _, err := client.Ping(context.Background()) + assert.Check(t, is.ErrorContains(err, "request returned Internal Server Error")) +}