From: Lennart Poettering Date: Fri, 12 Oct 2018 13:38:17 +0000 (+0200) Subject: json: enforce a maximum nesting depth for json variants X-Git-Tag: v240~473^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b2fa0d4fcab75f7012fc899dedfa68afeea9903c;p=thirdparty%2Fsystemd.git json: enforce a maximum nesting depth for json variants Simply as a safety precaution so that json objects we read are not arbitrary amounts deep, so that code that processes json objects recursively can't be easily exploited (by hitting stack limits). Follow-up for oss-fuzz#10908 (Nice is that we can accomodate for this counter without increasing the size of the JsonVariant object.) --- diff --git a/src/basic/json.c b/src/basic/json.c index b6106498bb7..8bf190ed586 100644 --- a/src/basic/json.c +++ b/src/basic/json.c @@ -24,6 +24,12 @@ #include "terminal-util.h" #include "utf8.h" +/* Refuse putting together variants with a larger depth than 16K by default (as a protection against overflowing stacks + * if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this + * remains under 2^16. */ +#define DEPTH_MAX (16U*1024U) +assert_cc(DEPTH_MAX <= UINT16_MAX); + typedef struct JsonSource { /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */ size_t n_ref; @@ -63,6 +69,9 @@ struct JsonVariant { /* While comparing two arrays, we use this for marking what we already have seen */ bool is_marked:1; + /* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */ + uint16_t depth; + union { /* For simple types we store the value in-line. */ JsonValue value; @@ -158,6 +167,18 @@ static JsonVariant *json_variant_dereference(JsonVariant *v) { return json_variant_dereference(v->reference); } +static uint16_t json_variant_depth(JsonVariant *v) { + + v = json_variant_dereference(v); + if (!v) + return 0; + + if (json_variant_is_magic(v)) + return 0; + + return v->depth; +} + static JsonVariant *json_variant_normalize(JsonVariant *v) { /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to @@ -413,8 +434,7 @@ static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) { } int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) { - JsonVariant *v; - size_t i; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; assert_return(ret, -EINVAL); if (n == 0) { @@ -430,22 +450,29 @@ int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) { *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, - .n_elements = n, }; - for (i = 0; i < n; i++) { - JsonVariant *w = v + 1 + i; + for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { + JsonVariant *w = v + 1 + v->n_elements, + *c = array[v->n_elements]; + uint16_t d; + + d = json_variant_depth(c); + if (d >= DEPTH_MAX) /* Refuse too deep nesting */ + return -ELNRNG; + if (d >= v->depth) + v->depth = d + 1; *w = (JsonVariant) { .is_embedded = true, .parent = v, }; - json_variant_set(w, array[i]); - json_variant_copy_source(w, array[i]); + json_variant_set(w, c); + json_variant_copy_source(w, c); } - *ret = v; + *ret = TAKE_PTR(v); return 0; } @@ -468,6 +495,7 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, .n_elements = n, + .depth = 1, }; for (i = 0; i < n; i++) { @@ -505,6 +533,7 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) { *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_ARRAY, + .depth = 1, }; for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { @@ -536,8 +565,7 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) { } int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { - JsonVariant *v; - size_t i; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; assert_return(ret, -EINVAL); if (n == 0) { @@ -554,22 +582,29 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { *v = (JsonVariant) { .n_ref = 1, .type = JSON_VARIANT_OBJECT, - .n_elements = n, }; - for (i = 0; i < n; i++) { - JsonVariant *w = v + 1 + i; + for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { + JsonVariant *w = v + 1 + v->n_elements, + *c = array[v->n_elements]; + uint16_t d; + + d = json_variant_depth(c); + if (d >= DEPTH_MAX) /* Refuse too deep nesting */ + return -ELNRNG; + if (d >= v->depth) + v->depth = d + 1; *w = (JsonVariant) { .is_embedded = true, .parent = v, }; - json_variant_set(w, array[i]); - json_variant_copy_source(w, array[i]); + json_variant_set(w, c); + json_variant_copy_source(w, c); } - *ret = v; + *ret = TAKE_PTR(v); return 0; } diff --git a/src/test/test-json.c b/src/test/test-json.c index e145559ae90..bcf94b211c2 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -354,6 +354,38 @@ static void test_source(void) { printf("--- pretty end ---\n"); } +static void test_depth(void) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *k = NULL; + unsigned i; + int r; + + assert_se(json_variant_new_string(&k, "hallo") >= 0); + v = json_variant_ref(k); + + /* Let's verify that the maximum depth checks work */ + + for (i = 0;; i++) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + assert_se(i <= UINT16_MAX); + if (i & 1) + r = json_variant_new_array(&w, &v, 1); + else + r = json_variant_new_object(&w, (JsonVariant*[]) { k, v }, 2); + if (r == -ELNRNG) { + log_info("max depth at %u", i); + break; + } + + assert_se(r >= 0); + + json_variant_unref(v); + v = TAKE_PTR(w); + } + + json_variant_dump(v, 0, stdout, NULL); +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); @@ -407,5 +439,7 @@ int main(int argc, char *argv[]) { test_source(); + test_depth(); + return 0; }