Ver código fonte

Merge pull request #19312 from cpuguy83/19177_fix_debug_req_dump_size

Don't dump request body when too large
Tibor Vass 9 anos atrás
pai
commit
59231ea6d8
2 arquivos alterados com 57 adições e 41 exclusões
  1. 34 21
      api/server/middleware.go
  2. 23 20
      pkg/authorization/authz.go

+ 34 - 21
api/server/middleware.go

@@ -1,9 +1,9 @@
 package server
 
 import (
-	"bytes"
+	"bufio"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"runtime"
 	"strings"
@@ -14,6 +14,7 @@ import (
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/authorization"
+	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/version"
 	"golang.org/x/net/context"
 )
@@ -27,25 +28,37 @@ func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 		logrus.Debugf("%s %s", r.Method, r.RequestURI)
 
-		if r.Method == "POST" {
-			if err := httputils.CheckForJSON(r); err == nil {
-				var buf bytes.Buffer
-				if _, err := buf.ReadFrom(r.Body); err == nil {
-					r.Body.Close()
-					r.Body = ioutil.NopCloser(&buf)
-					var postForm map[string]interface{}
-					if err := json.Unmarshal(buf.Bytes(), &postForm); err == nil {
-						if _, exists := postForm["password"]; exists {
-							postForm["password"] = "*****"
-						}
-						formStr, errMarshal := json.Marshal(postForm)
-						if errMarshal == nil {
-							logrus.Debugf("form data: %s", string(formStr))
-						} else {
-							logrus.Debugf("form data: %q", postForm)
-						}
-					}
-				}
+		if r.Method != "POST" {
+			return handler(ctx, w, r, vars)
+		}
+		if err := httputils.CheckForJSON(r); err != nil {
+			return handler(ctx, w, r, vars)
+		}
+		maxBodySize := 4096 // 4KB
+		if r.ContentLength > int64(maxBodySize) {
+			return handler(ctx, w, r, vars)
+		}
+
+		body := r.Body
+		bufReader := bufio.NewReaderSize(body, maxBodySize)
+		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
+
+		b, err := bufReader.Peek(maxBodySize)
+		if err != io.EOF {
+			// either there was an error reading, or the buffer is full (in which case the request is too large)
+			return handler(ctx, w, r, vars)
+		}
+
+		var postForm map[string]interface{}
+		if err := json.Unmarshal(b, &postForm); err == nil {
+			if _, exists := postForm["password"]; exists {
+				postForm["password"] = "*****"
+			}
+			formStr, errMarshal := json.Marshal(postForm)
+			if errMarshal == nil {
+				logrus.Debugf("form data: %s", string(formStr))
+			} else {
+				logrus.Debugf("form data: %q", postForm)
 			}
 		}
 

+ 23 - 20
pkg/authorization/authz.go

@@ -1,16 +1,19 @@
 package authorization
 
 import (
+	"bufio"
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"strings"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/ioutils"
 )
 
+const maxBodySize = 1048576 // 1MB
+
 // NewCtx creates new authZ context, it is used to store authorization information related to a specific docker
 // REST http session
 // A context provides two method:
@@ -52,18 +55,12 @@ type Ctx struct {
 func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
 	var body []byte
 	if sendBody(ctx.requestURI, r.Header) {
-		var (
-			err         error
-			drainedBody io.ReadCloser
-		)
-		drainedBody, r.Body, err = drainBody(r.Body)
-		if err != nil {
-			return err
-		}
-		defer drainedBody.Close()
-		body, err = ioutil.ReadAll(drainedBody)
-		if err != nil {
-			return err
+		if r.ContentLength < maxBodySize {
+			var err error
+			body, r.Body, err = drainBody(r.Body)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -126,15 +123,21 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
 
 // drainBody dump the body, it reads the body data into memory and
 // see go sources /go/src/net/http/httputil/dump.go
-func drainBody(b io.ReadCloser) (io.ReadCloser, io.ReadCloser, error) {
-	var buf bytes.Buffer
-	if _, err := buf.ReadFrom(b); err != nil {
-		return nil, nil, err
-	}
-	if err := b.Close(); err != nil {
+func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
+	bufReader := bufio.NewReaderSize(body, maxBodySize)
+	newBody := ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
+
+	data, err := bufReader.Peek(maxBodySize)
+	if err != io.EOF {
+		// This means the request is larger than our max
+		if err == bufio.ErrBufferFull {
+			return nil, newBody, nil
+		}
+		// This means we had an error reading
 		return nil, nil, err
 	}
-	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
+
+	return data, newBody, nil
 }
 
 // sendBody returns true when request/response body should be sent to AuthZPlugin