diff --git a/src/httpserver.cpp b/src/httpserver.cpp index b6d810531c9..feac7c1472c 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -39,6 +39,11 @@ #include #include +/** Maximum size (in bytes) of an HTTP request body we will accept. The RPC + * interface never needs anywhere near MAX_SIZE; cap it to bound memory use + * from a single connection. */ +static const size_t MAX_HTTP_REQUEST_SIZE = 10 * 1024 * 1024; + /** HTTP request work item */ class HTTPWorkItem : public HTTPClosure { @@ -419,7 +424,7 @@ bool InitHTTPServer() } evhttp_set_timeout(http, GetArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT)); - evhttp_set_max_body_size(http, MAX_SIZE); + evhttp_set_max_body_size(http, MAX_HTTP_REQUEST_SIZE); evhttp_set_gencb(http, http_request_cb, NULL); if (!HTTPBindAddresses(http)) { diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 07fbb148ae3..c1cef245166 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -435,8 +435,15 @@ static UniValue JSONRPCExecOne(const UniValue& req) return rpc_result; } +// Maximum number of requests permitted in a single JSON-RPC batch, to guard +// against a single connection queuing an unbounded amount of work. +static const size_t MAX_BATCH_SIZE = 100; + std::string JSONRPCExecBatch(const UniValue& vReq) { + if (vReq.size() > MAX_BATCH_SIZE) + throw JSONRPCError(RPC_INVALID_REQUEST, "Batch request size exceeds maximum"); + UniValue ret(UniValue::VARR); for (size_t reqIdx = 0; reqIdx < vReq.size(); reqIdx++) ret.push_back(JSONRPCExecOne(vReq[reqIdx])); diff --git a/src/univalue/lib/univalue_read.cpp b/src/univalue/lib/univalue_read.cpp index 7a9acdd75fd..e4427de1a25 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -10,6 +10,10 @@ using namespace std; +// Maximum nesting depth of objects/arrays accepted by the parser, to guard +// against stack exhaustion from deeply-nested untrusted JSON input. +static const size_t MAX_JSON_DEPTH = 512; + static bool json_isdigit(int ch) { return ((ch >= '0') && (ch <= '9')); @@ -315,6 +319,8 @@ bool UniValue::read(const char *raw, size_t size) setObject(); else setArray(); + if (stack.size() >= MAX_JSON_DEPTH) + return false; stack.push_back(this); } else { UniValue tmpVal(utyp); @@ -322,6 +328,8 @@ bool UniValue::read(const char *raw, size_t size) top->values.push_back(tmpVal); UniValue *newTop = &(top->values.back()); + if (stack.size() >= MAX_JSON_DEPTH) + return false; stack.push_back(newTop); } diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 37a07ea06bd..b175fc4d6af 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -9,6 +9,7 @@ #ifndef BITCOIN_UTILSTRENCODINGS_H #define BITCOIN_UTILSTRENCODINGS_H +#include #include #include #include @@ -113,16 +114,19 @@ std::string FormatParagraph(const std::string& in, size_t width = 79, size_t ind /** * Timing-attack-resistant comparison. - * Takes time proportional to length - * of first argument. + * Takes time proportional to the longer of the two arguments, so that the + * comparison length does not depend on which argument is shorter (the previous + * implementation looped a.size() times and indexed b[i%b.size()], leaking the + * relative lengths through timing). */ template bool TimingResistantEqual(const T& a, const T& b) { if (b.size() == 0) return a.size() == 0; size_t accumulator = a.size() ^ b.size(); - for (size_t i = 0; i < a.size(); i++) - accumulator |= a[i] ^ b[i%b.size()]; + size_t n = std::max(a.size(), b.size()); + for (size_t i = 0; i < n; i++) + accumulator |= (size_t)((i < a.size() ? a[i] : 0) ^ (i < b.size() ? b[i] : 0)); return accumulator == 0; }