diff --git a/http/handler.go b/http/handler.go index 775fce9970..f927a43513 100644 --- a/http/handler.go +++ b/http/handler.go @@ -26,6 +26,11 @@ const ( // NoRequestForwardingHeaderName is the name of the header telling Vault // not to use request forwarding NoRequestForwardingHeaderName = "X-Vault-No-Request-Forwarding" + + // MaxRequestSize is the maximum accepted request size. This is to prevent + // a denial of service attack where no Content-Length is provided and the server + // is fed ever more data until it exhausts memory. + MaxRequestSize = 32 * 1024 * 1024 ) // Handler returns an http.Handler for the API. This can be used on @@ -109,7 +114,10 @@ func stripPrefix(prefix, path string) (string, bool) { } func parseRequest(r *http.Request, out interface{}) error { - err := jsonutil.DecodeJSONFromReader(r.Body, out) + // Limit the maximum number of bytes to MaxRequestSize to protect + // against an indefinite amount of data being read. + limit := &io.LimitedReader{R: r.Body, N: MaxRequestSize} + err := jsonutil.DecodeJSONFromReader(limit, out) if err != nil && err != io.EOF { return fmt.Errorf("Failed to parse JSON input: %s", err) } diff --git a/http/logical.go b/http/logical.go index edf1eabf9b..6e64478978 100644 --- a/http/logical.go +++ b/http/logical.go @@ -26,6 +26,11 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques return nil, http.StatusNotFound, nil } + // Verify the content length does not exceed the maximum size + if r.ContentLength >= MaxRequestSize { + return nil, http.StatusRequestEntityTooLarge, nil + } + // Determine the operation var op logical.Operation switch r.Method { diff --git a/http/logical_test.go b/http/logical_test.go index 161261686d..8149a5c4f9 100644 --- a/http/logical_test.go +++ b/http/logical_test.go @@ -231,3 +231,16 @@ func TestLogical_RawHTTP(t *testing.T) { t.Fatalf("Bad: %s", body.Bytes()) } } + +func TestLogical_RequestSizeLimit(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + // Write a very large object, should fail + resp := testHttpPut(t, token, addr+"/v1/secret/foo", map[string]interface{}{ + "data": make([]byte, MaxRequestSize), + }) + testResponseStatus(t, resp, 413) +}