diff --git a/include/neuron/define.h b/include/neuron/define.h index da4638864..8d8590b5e 100644 --- a/include/neuron/define.h +++ b/include/neuron/define.h @@ -57,6 +57,10 @@ #define NEU_MSG_MAX_SIZE 2048 #define NEU_PATH_LEN 128 +#define NEU_HTTP_BODY_MAX_SIZE (1 * 1024 * 1024) +#define NEU_HTTP_BODY_MAX_SIZE_LARGE (4 * 1024 * 1024) +#define NEU_HTTP_BODY_TOO_BIG (-2) + #define NEU_TAG_META_LENGTH 20 #define NEU_TAG_META_SIZE 32 #define NEU_TAG_FORMAT_LENGTH 16 diff --git a/include/neuron/utils/http.h b/include/neuron/utils/http.h index 414db033f..5166ab01c 100644 --- a/include/neuron/utils/http.h +++ b/include/neuron/utils/http.h @@ -35,6 +35,10 @@ extern "C" { // buffer overflow. ssize_t neu_url_decode(const char *s, size_t len, char *buf, size_t size); +// Get a NUL terminated copy of the request body, which the caller must free. +// Returns 0 on success, NEU_HTTP_BODY_TOO_BIG if the body exceeds +// NEU_HTTP_BODY_MAX_SIZE_LARGE, and -1 if the body is empty or on allocation +// failure. On failure `*data` is NULL and `*data_size` is zero. int neu_http_get_body(nng_aio *aio, void **data, size_t *data_size); // Find query parameter value of the given name. diff --git a/include/neuron/utils/http_handler.h b/include/neuron/utils/http_handler.h index cdc11866e..7b992c214 100644 --- a/include/neuron/utils/http_handler.h +++ b/include/neuron/utils/http_handler.h @@ -25,24 +25,27 @@ #include #include +#include "define.h" #include "utils/neu_jwt.h" #include "json/neu_json_error.h" -#define NEU_PROCESS_HTTP_REQUEST(aio, req_type, decode_fun, func) \ - { \ - char * req_data = NULL; \ - size_t req_data_size = 0; \ - req_type *req = NULL; \ - \ - if (neu_http_get_body((aio), (void **) &req_data, &req_data_size) == \ - 0 && \ - decode_fun(req_data, &req) == 0) { \ - { func }; \ - decode_fun##_free(req); \ - } else { \ - neu_http_bad_request(aio, "{\"error\": 1002}"); \ - } \ - free(req_data); \ +#define NEU_PROCESS_HTTP_REQUEST(aio, req_type, decode_fun, func) \ + { \ + char * req_data = NULL; \ + size_t req_data_size = 0; \ + req_type *req = NULL; \ + int body_rv = \ + neu_http_get_body((aio), (void **) &req_data, &req_data_size); \ + \ + if (0 == body_rv && decode_fun(req_data, &req) == 0) { \ + { func }; \ + decode_fun##_free(req); \ + } else if (NEU_HTTP_BODY_TOO_BIG == body_rv) { \ + neu_http_bad_request(aio, "{\"error\": 1017}"); \ + } else { \ + neu_http_bad_request(aio, "{\"error\": 1002}"); \ + } \ + free(req_data); \ } #define NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT(aio, req_type, decode_fun, func) \ @@ -119,6 +122,8 @@ struct neu_http_handler { char *path; char *dst_url; } value; + + size_t max_body; }; int neu_http_add_handler(nng_http_server * server, diff --git a/plugins/restful/handle.c b/plugins/restful/handle.c index 5c8536319..bec0820f8 100644 --- a/plugins/restful/handle.c +++ b/plugins/restful/handle.c @@ -269,25 +269,28 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/tags", .value.handler = handle_add_tags, - + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_POST, .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/gtags", .value.handler = handle_add_gtags, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_POST, .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/tags/import", .value.handler = handle_import_tags, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_PUT, .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/tags", .value.handler = handle_update_tags, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_GET, @@ -408,12 +411,14 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/write/tags", .value.handler = handle_write_tags, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_POST, .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/write/gtags", .value.handler = handle_write_gtags, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_POST, @@ -450,6 +455,7 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/subscribes", .value.handler = handle_grp_subscribes, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_GET, @@ -516,12 +522,14 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/global/config", .value.handler = handle_put_global_config, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_PUT, .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/global/drivers", .value.handler = handle_put_drivers, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_GET, @@ -540,6 +548,7 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/global/apps", .value.handler = handle_put_apps, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_GET, @@ -746,6 +755,7 @@ static struct neu_http_handler rest_handlers[] = { .type = NEU_HTTP_HANDLER_FUNCTION, .url = "/api/v2/simulator/config", .value.handler = handle_simulator_set_config, + .max_body = NEU_HTTP_BODY_MAX_SIZE_LARGE, }, { .method = NEU_HTTP_METHOD_GET, diff --git a/src/utils/http.c b/src/utils/http.c index bfe770636..a2917a799 100644 --- a/src/utils/http.c +++ b/src/utils/http.c @@ -100,8 +100,18 @@ int neu_http_get_body(nng_aio *aio, void **data, size_t *data_size) nng_http_req_get_data(req, data, data_size); if (*data_size == 0) { return -1; + } else if (*data_size > NEU_HTTP_BODY_MAX_SIZE_LARGE) { + nlog_error("<%p> request body too big: %zu", aio, *data_size); + *data = NULL; + *data_size = 0; + return NEU_HTTP_BODY_TOO_BIG; } else { char *buf = calloc(*data_size + 1, sizeof(char)); + if (NULL == buf) { + *data = NULL; + *data_size = 0; + return -1; + } memcpy(buf, *data, *data_size); *data = buf; return 0; diff --git a/src/utils/http_handler.c b/src/utils/http_handler.c index ef5f8334f..2e2421240 100644 --- a/src/utils/http_handler.c +++ b/src/utils/http_handler.c @@ -24,6 +24,7 @@ #include #include +#include "define.h" #include "utils/http_handler.h" #include "utils/log.h" @@ -53,6 +54,12 @@ int neu_http_add_handler(nng_http_server * server, return -1; } + if (NEU_HTTP_HANDLER_FUNCTION == http_handler->type) { + size_t max_body = http_handler->max_body > 0 ? http_handler->max_body + : NEU_HTTP_BODY_MAX_SIZE; + nng_http_handler_collect_body(handler, true, max_body); + } + switch (http_handler->method) { case NEU_HTTP_METHOD_GET: ret = nng_http_handler_set_method(handler, "GET"); diff --git a/tests/ft/http_api/test_http_api.py b/tests/ft/http_api/test_http_api.py index 1ca4ba417..db1cce083 100644 --- a/tests/ft/http_api/test_http_api.py +++ b/tests/ft/http_api/test_http_api.py @@ -1,5 +1,7 @@ import copy +import requests + import neuron.api as api import neuron.error as error import neuron.config as config @@ -649,6 +651,23 @@ def test_get_node_or_group_by_filter(self): assert 1 == len(response.json()["groups"]) + @description(given="running neuron", when="post an over sized body", then="should be rejected before parsing") + def test_body_too_big(self): + # far above the 1MB cap of an ordinary endpoint, but below the 4MB + # cap of the bulk endpoints + payload = '{"name": "' + 'a' * (2 * 1024 * 1024) + '"}' + headers = {"Authorization": config.default_jwt, + "Content-Type": "application/json"} + + response = requests.post( + url=config.BASE_URL + '/api/v2/node', headers=headers, data=payload) + assert 400 == response.status_code + assert error.NEU_ERR_BODY_TOO_BIG == response.json()['error'] + + # neuron is still alive and serving after the rejection + response = api.get_version() + assert 200 == response.status_code + @description(given="running neuron", when="test jwt error", then="failed") def test_jwt_err(self): response = api.change_password(new_password='123456', jwt='invalid') diff --git a/tests/ft/neuron/error.py b/tests/ft/neuron/error.py index 7a6f3cda6..038bb835a 100644 --- a/tests/ft/neuron/error.py +++ b/tests/ft/neuron/error.py @@ -16,8 +16,8 @@ NEU_ERR_COMMAND_EXECUTION_FAILED = 1014 NEU_ERR_IP_ADDRESS_INVALID = 1015 NEU_ERR_IP_ADDRESS_IN_USE = 1016 -NEU_ERR_INVALID_USER = 1017 -NEU_ERR_INVALID_PASSWORD = 1018 +NEU_ERR_BODY_TOO_BIG = 1017 +NEU_ERR_INVALID_CID = 1018 NEU_ERR_NODE_EXIST = 2002 NEU_ERR_NODE_NOT_EXIST = 2003