Browse code

Merge pull request #19312 from cpuguy83/19177_fix_debug_req_dump_size

Don't dump request body when too large

Tibor Vass authored on 2016/01/14 23:06:02
Showing 2 changed files
... ...
@@ -1,9 +1,9 @@
1 1
 package server
2 2
 
3 3
 import (
4
-	"bytes"
4
+	"bufio"
5 5
 	"encoding/json"
6
-	"io/ioutil"
6
+	"io"
7 7
 	"net/http"
8 8
 	"runtime"
9 9
 	"strings"
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/dockerversion"
15 15
 	"github.com/docker/docker/errors"
16 16
 	"github.com/docker/docker/pkg/authorization"
17
+	"github.com/docker/docker/pkg/ioutils"
17 18
 	"github.com/docker/docker/pkg/version"
18 19
 	"golang.org/x/net/context"
19 20
 )
... ...
@@ -27,25 +28,37 @@ func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
27 27
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
28 28
 		logrus.Debugf("%s %s", r.Method, r.RequestURI)
29 29
 
30
-		if r.Method == "POST" {
31
-			if err := httputils.CheckForJSON(r); err == nil {
32
-				var buf bytes.Buffer
33
-				if _, err := buf.ReadFrom(r.Body); err == nil {
34
-					r.Body.Close()
35
-					r.Body = ioutil.NopCloser(&buf)
36
-					var postForm map[string]interface{}
37
-					if err := json.Unmarshal(buf.Bytes(), &postForm); err == nil {
38
-						if _, exists := postForm["password"]; exists {
39
-							postForm["password"] = "*****"
40
-						}
41
-						formStr, errMarshal := json.Marshal(postForm)
42
-						if errMarshal == nil {
43
-							logrus.Debugf("form data: %s", string(formStr))
44
-						} else {
45
-							logrus.Debugf("form data: %q", postForm)
46
-						}
47
-					}
48
-				}
30
+		if r.Method != "POST" {
31
+			return handler(ctx, w, r, vars)
32
+		}
33
+		if err := httputils.CheckForJSON(r); err != nil {
34
+			return handler(ctx, w, r, vars)
35
+		}
36
+		maxBodySize := 4096 // 4KB
37
+		if r.ContentLength > int64(maxBodySize) {
38
+			return handler(ctx, w, r, vars)
39
+		}
40
+
41
+		body := r.Body
42
+		bufReader := bufio.NewReaderSize(body, maxBodySize)
43
+		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
44
+
45
+		b, err := bufReader.Peek(maxBodySize)
46
+		if err != io.EOF {
47
+			// either there was an error reading, or the buffer is full (in which case the request is too large)
48
+			return handler(ctx, w, r, vars)
49
+		}
50
+
51
+		var postForm map[string]interface{}
52
+		if err := json.Unmarshal(b, &postForm); err == nil {
53
+			if _, exists := postForm["password"]; exists {
54
+				postForm["password"] = "*****"
55
+			}
56
+			formStr, errMarshal := json.Marshal(postForm)
57
+			if errMarshal == nil {
58
+				logrus.Debugf("form data: %s", string(formStr))
59
+			} else {
60
+				logrus.Debugf("form data: %q", postForm)
49 61
 			}
50 62
 		}
51 63
 
... ...
@@ -1,16 +1,19 @@
1 1
 package authorization
2 2
 
3 3
 import (
4
+	"bufio"
4 5
 	"bytes"
5 6
 	"fmt"
6 7
 	"io"
7
-	"io/ioutil"
8 8
 	"net/http"
9 9
 	"strings"
10 10
 
11 11
 	"github.com/Sirupsen/logrus"
12
+	"github.com/docker/docker/pkg/ioutils"
12 13
 )
13 14
 
15
+const maxBodySize = 1048576 // 1MB
16
+
14 17
 // NewCtx creates new authZ context, it is used to store authorization information related to a specific docker
15 18
 // REST http session
16 19
 // A context provides two method:
... ...
@@ -52,18 +55,12 @@ type Ctx struct {
52 52
 func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
53 53
 	var body []byte
54 54
 	if sendBody(ctx.requestURI, r.Header) {
55
-		var (
56
-			err         error
57
-			drainedBody io.ReadCloser
58
-		)
59
-		drainedBody, r.Body, err = drainBody(r.Body)
60
-		if err != nil {
61
-			return err
62
-		}
63
-		defer drainedBody.Close()
64
-		body, err = ioutil.ReadAll(drainedBody)
65
-		if err != nil {
66
-			return err
55
+		if r.ContentLength < maxBodySize {
56
+			var err error
57
+			body, r.Body, err = drainBody(r.Body)
58
+			if err != nil {
59
+				return err
60
+			}
67 61
 		}
68 62
 	}
69 63
 
... ...
@@ -126,15 +123,21 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
126 126
 
127 127
 // drainBody dump the body, it reads the body data into memory and
128 128
 // see go sources /go/src/net/http/httputil/dump.go
129
-func drainBody(b io.ReadCloser) (io.ReadCloser, io.ReadCloser, error) {
130
-	var buf bytes.Buffer
131
-	if _, err := buf.ReadFrom(b); err != nil {
132
-		return nil, nil, err
133
-	}
134
-	if err := b.Close(); err != nil {
129
+func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
130
+	bufReader := bufio.NewReaderSize(body, maxBodySize)
131
+	newBody := ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
132
+
133
+	data, err := bufReader.Peek(maxBodySize)
134
+	if err != io.EOF {
135
+		// This means the request is larger than our max
136
+		if err == bufio.ErrBufferFull {
137
+			return nil, newBody, nil
138
+		}
139
+		// This means we had an error reading
135 140
 		return nil, nil, err
136 141
 	}
137
-	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
142
+
143
+	return data, newBody, nil
138 144
 }
139 145
 
140 146
 // sendBody returns true when request/response body should be sent to AuthZPlugin