Skip to content

Commit b2fa0d4

Browse files
committed
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.)
1 parent a7efb03 commit b2fa0d4

File tree

2 files changed

+85
-16
lines changed

2 files changed

+85
-16
lines changed

src/basic/json.c

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
#include "terminal-util.h"
2525
#include "utf8.h"
2626

27+
/* Refuse putting together variants with a larger depth than 16K by default (as a protection against overflowing stacks
28+
* if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this
29+
* remains under 2^16. */
30+
#define DEPTH_MAX (16U*1024U)
31+
assert_cc(DEPTH_MAX <= UINT16_MAX);
32+
2733
typedef struct JsonSource {
2834
/* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */
2935
size_t n_ref;
@@ -63,6 +69,9 @@ struct JsonVariant {
6369
/* While comparing two arrays, we use this for marking what we already have seen */
6470
bool is_marked:1;
6571

72+
/* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */
73+
uint16_t depth;
74+
6675
union {
6776
/* For simple types we store the value in-line. */
6877
JsonValue value;
@@ -158,6 +167,18 @@ static JsonVariant *json_variant_dereference(JsonVariant *v) {
158167
return json_variant_dereference(v->reference);
159168
}
160169

170+
static uint16_t json_variant_depth(JsonVariant *v) {
171+
172+
v = json_variant_dereference(v);
173+
if (!v)
174+
return 0;
175+
176+
if (json_variant_is_magic(v))
177+
return 0;
178+
179+
return v->depth;
180+
}
181+
161182
static JsonVariant *json_variant_normalize(JsonVariant *v) {
162183

163184
/* 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) {
413434
}
414435

415436
int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
416-
JsonVariant *v;
417-
size_t i;
437+
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
418438

419439
assert_return(ret, -EINVAL);
420440
if (n == 0) {
@@ -430,22 +450,29 @@ int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
430450
*v = (JsonVariant) {
431451
.n_ref = 1,
432452
.type = JSON_VARIANT_ARRAY,
433-
.n_elements = n,
434453
};
435454

436-
for (i = 0; i < n; i++) {
437-
JsonVariant *w = v + 1 + i;
455+
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
456+
JsonVariant *w = v + 1 + v->n_elements,
457+
*c = array[v->n_elements];
458+
uint16_t d;
459+
460+
d = json_variant_depth(c);
461+
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
462+
return -ELNRNG;
463+
if (d >= v->depth)
464+
v->depth = d + 1;
438465

439466
*w = (JsonVariant) {
440467
.is_embedded = true,
441468
.parent = v,
442469
};
443470

444-
json_variant_set(w, array[i]);
445-
json_variant_copy_source(w, array[i]);
471+
json_variant_set(w, c);
472+
json_variant_copy_source(w, c);
446473
}
447474

448-
*ret = v;
475+
*ret = TAKE_PTR(v);
449476
return 0;
450477
}
451478

@@ -468,6 +495,7 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) {
468495
.n_ref = 1,
469496
.type = JSON_VARIANT_ARRAY,
470497
.n_elements = n,
498+
.depth = 1,
471499
};
472500

473501
for (i = 0; i < n; i++) {
@@ -505,6 +533,7 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) {
505533
*v = (JsonVariant) {
506534
.n_ref = 1,
507535
.type = JSON_VARIANT_ARRAY,
536+
.depth = 1,
508537
};
509538

510539
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) {
536565
}
537566

538567
int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
539-
JsonVariant *v;
540-
size_t i;
568+
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
541569

542570
assert_return(ret, -EINVAL);
543571
if (n == 0) {
@@ -554,22 +582,29 @@ int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
554582
*v = (JsonVariant) {
555583
.n_ref = 1,
556584
.type = JSON_VARIANT_OBJECT,
557-
.n_elements = n,
558585
};
559586

560-
for (i = 0; i < n; i++) {
561-
JsonVariant *w = v + 1 + i;
587+
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
588+
JsonVariant *w = v + 1 + v->n_elements,
589+
*c = array[v->n_elements];
590+
uint16_t d;
591+
592+
d = json_variant_depth(c);
593+
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
594+
return -ELNRNG;
595+
if (d >= v->depth)
596+
v->depth = d + 1;
562597

563598
*w = (JsonVariant) {
564599
.is_embedded = true,
565600
.parent = v,
566601
};
567602

568-
json_variant_set(w, array[i]);
569-
json_variant_copy_source(w, array[i]);
603+
json_variant_set(w, c);
604+
json_variant_copy_source(w, c);
570605
}
571606

572-
*ret = v;
607+
*ret = TAKE_PTR(v);
573608
return 0;
574609
}
575610

src/test/test-json.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,38 @@ static void test_source(void) {
354354
printf("--- pretty end ---\n");
355355
}
356356

357+
static void test_depth(void) {
358+
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *k = NULL;
359+
unsigned i;
360+
int r;
361+
362+
assert_se(json_variant_new_string(&k, "hallo") >= 0);
363+
v = json_variant_ref(k);
364+
365+
/* Let's verify that the maximum depth checks work */
366+
367+
for (i = 0;; i++) {
368+
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
369+
370+
assert_se(i <= UINT16_MAX);
371+
if (i & 1)
372+
r = json_variant_new_array(&w, &v, 1);
373+
else
374+
r = json_variant_new_object(&w, (JsonVariant*[]) { k, v }, 2);
375+
if (r == -ELNRNG) {
376+
log_info("max depth at %u", i);
377+
break;
378+
}
379+
380+
assert_se(r >= 0);
381+
382+
json_variant_unref(v);
383+
v = TAKE_PTR(w);
384+
}
385+
386+
json_variant_dump(v, 0, stdout, NULL);
387+
}
388+
357389
int main(int argc, char *argv[]) {
358390

359391
log_set_max_level(LOG_DEBUG);
@@ -407,5 +439,7 @@ int main(int argc, char *argv[]) {
407439

408440
test_source();
409441

442+
test_depth();
443+
410444
return 0;
411445
}

0 commit comments

Comments
 (0)