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.)
This commit is contained in:
parent
a7efb03039
commit
b2fa0d4fca
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue