]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json: enforce a maximum nesting depth for json variants
authorLennart Poettering <lennart@poettering.net>
Fri, 12 Oct 2018 13:38:17 +0000 (15:38 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 18 Oct 2018 14:44:51 +0000 (16:44 +0200)
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.)

src/basic/json.c
src/test/test-json.c

index b6106498bb7def7f22eefd11504ae243021eacf4..8bf190ed586d1d24a5eba78d3bf6f5e98cc713e2 100644 (file)
 #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;
 }
 
index e145559ae903feb4e5b969cfab5b100504c45c10..bcf94b211c2e4752c536364a9f7014284f938667 100644 (file)
@@ -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;
 }