From dcc3463e480bc5e079273f5c2d8941a64ed85007 Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Thu, 28 Jul 2016 21:02:22 +0200 Subject: [PATCH] Limit POST form fields and file size + Generic Request Size limiter (#2317) * Use less memory when receiving a file via multipart * Add generic http request maximum size limiter to secure against malicious clients --- bucket-handlers.go | 2 +- generic-handlers.go | 21 +++++++++++++++++++++ globals.go | 6 ++++++ handler-utils.go | 23 ++++++++++++----------- routers.go | 2 ++ typed-errors.go | 3 +++ 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/bucket-handlers.go b/bucket-handlers.go index a1fc27e47..ff964c475 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -347,7 +347,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - fileBody, fileName, formValues, err := extractHTTPFormValues(reader) + fileBody, fileName, formValues, err := extractPostPolicyFormValues(reader) if err != nil { errorIf(err, "Unable to parse form values.") writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path) diff --git a/generic-handlers.go b/generic-handlers.go index e9baff18e..dc701b8c6 100644 --- a/generic-handlers.go +++ b/generic-handlers.go @@ -39,6 +39,27 @@ func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handle return f } +// Adds limiting body size middleware + +// Set the body size limit to 6 Gb = Maximum object size + other possible data +// in the same request +const requestMaxBodySize = 1024 * 1024 * 1024 * (5 + 1) + +type requestSizeLimitHandler struct { + handler http.Handler + maxBodySize int64 +} + +func setRequestSizeLimitHandler(h http.Handler) http.Handler { + return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize} +} + +func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Restricting read data to a given maximum length + r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize) + h.handler.ServeHTTP(w, r) +} + // Adds redirect rules for incoming requests. type redirectHandler struct { handler http.Handler diff --git a/globals.go b/globals.go index 5ebb1ca8f..842b7b8ba 100644 --- a/globals.go +++ b/globals.go @@ -52,6 +52,12 @@ var ( // Add new variable global values here. ) +var ( + // Limit fields size (except file) to 1Mib since Policy document + // can reach that size according to https://aws.amazon.com/articles/1434 + maxFormFieldSize = int64(1024 * 1024) +) + // global colors. var ( colorBlue = color.New(color.FgBlue).SprintfFunc() diff --git a/handler-utils.go b/handler-utils.go index 987a29520..6dfc505e5 100644 --- a/handler-utils.go +++ b/handler-utils.go @@ -17,7 +17,6 @@ package main import ( - "bytes" "io" "io/ioutil" "mime/multipart" @@ -99,12 +98,11 @@ func extractMetadataFromHeader(header http.Header) map[string]string { return metadata } -func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, string, map[string]string, error) { +// Extract form fields and file data from a HTTP POST Policy +func extractPostPolicyFormValues(reader *multipart.Reader) (filePart io.Reader, fileName string, formValues map[string]string, err error) { /// HTML Form values - formValues := make(map[string]string) - filePart := new(bytes.Buffer) - fileName := "" - var err error + formValues = make(map[string]string) + fileName = "" for err == nil { var part *multipart.Part part, err = reader.NextPart() @@ -112,19 +110,22 @@ func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, string, map[str canonicalFormName := http.CanonicalHeaderKey(part.FormName()) if canonicalFormName != "File" { var buffer []byte - buffer, err = ioutil.ReadAll(part) + limitReader := io.LimitReader(part, maxFormFieldSize+1) + buffer, err = ioutil.ReadAll(limitReader) if err != nil { return nil, "", nil, err } + if int64(len(buffer)) > maxFormFieldSize { + return nil, "", nil, errSizeUnexpected + } formValues[canonicalFormName] = string(buffer) } else { - if _, err = io.Copy(filePart, part); err != nil { - return nil, "", nil, err - } + filePart = io.LimitReader(part, maxObjectSize) fileName = part.FileName() + // As described in S3 spec, we expect file to be the last form field + break } } } return filePart, fileName, formValues, nil - } diff --git a/routers.go b/routers.go index 79189503a..ad615893e 100644 --- a/routers.go +++ b/routers.go @@ -79,6 +79,8 @@ func configureServerHandler(srvCmdConfig serverCmdConfig) http.Handler { var handlerFns = []HandlerFunc{ // Limits the number of concurrent http requests. setRateLimitHandler, + // Limits all requests size to a maximum fixed limit + setRequestSizeLimitHandler, // Adds 'crossdomain.xml' policy handler to serve legacy flash clients. setCrossDomainPolicy, // Redirect some pre-defined browser request paths to a static location prefix. diff --git a/typed-errors.go b/typed-errors.go index c7bc6083f..0155d947e 100644 --- a/typed-errors.go +++ b/typed-errors.go @@ -32,3 +32,6 @@ var errInvalidToken = errors.New("Invalid token") // If x-amz-content-sha256 header value mismatches with what we calculate. var errContentSHA256Mismatch = errors.New("sha256 mismatch") + +// used when we deal with data larger than expected +var errSizeUnexpected = errors.New("data size larger than expected")