diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c05a177d..2e400227c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - Native/macOS: fix module `image_size` computation, which could have caused the symbolicator to misattribute every frame to the lowest-addressed image (typically `dyld` or `libsystem`). ([#1740](https://github.com/getsentry/sentry-native/pull/1740)) - Native: raise `SENTRY_CRASH_MAX_MODULES` from `512` to `2048` so processes that load many shared libraries no longer have their minidump module list truncated, which left frames in unrecorded modules without a `debug_id` and unsymbolicatable. ([#1738](https://github.com/getsentry/sentry-native/pull/1738)) -- Reject overly deep msgpack payloads during deserialization. ([#1727](https://github.com/getsentry/sentry-native/pull/1727)) +- Reject overly deep JSON and msgpack payloads during deserialization. ([#1727](https://github.com/getsentry/sentry-native/pull/1727), [#1748](https://github.com/getsentry/sentry-native/pull/1748)) - Read lengths for variadic fingerprints. ([#1730](https://github.com/getsentry/sentry-native/pull/1730)) - Guard against JSON token allocation overflow on 32-bit platforms. ([#1733](https://github.com/getsentry/sentry-native/pull/1733)) - Windows: fix HTTP rate limit response header parsing. ([#1732](https://github.com/getsentry/sentry-native/pull/1732)) diff --git a/src/sentry_json.c b/src/sentry_json.c index 7451b14cf..2f691be4c 100644 --- a/src/sentry_json.c +++ b/src/sentry_json.c @@ -21,6 +21,8 @@ #include "sentry_utils.h" #include "sentry_value.h" +#define SENTRY_JSON_MAX_DEPTH 64 + typedef struct { void (*free)(sentry_jsonwriter_t *writer); void (*write_str)(sentry_jsonwriter_t *writer, const char *str); @@ -207,7 +209,7 @@ sentry__jsonwriter_into_string(sentry_jsonwriter_t *jw, size_t *len_out) static bool at_max_depth(const sentry_jsonwriter_t *jw) { - return jw->depth >= 64; + return jw->depth >= SENTRY_JSON_MAX_DEPTH; } static void @@ -578,7 +580,7 @@ decode_string_inplace(char *buf) static size_t tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, - sentry_value_t *value_out) + size_t depth, sentry_value_t *value_out) { size_t offset = 0; @@ -586,7 +588,7 @@ tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, #define NESTED_PARSE(Target) \ do { \ size_t child_consumed = tokens_to_value( \ - tokens + offset, token_count - offset, buf, Target); \ + tokens + offset, token_count - offset, buf, depth + 1, Target); \ if (child_consumed == (size_t)-1) { \ goto error; \ } \ @@ -665,6 +667,9 @@ tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, break; } case JSMN_OBJECT: { + if (depth >= SENTRY_JSON_MAX_DEPTH) { + goto error; + } rv = sentry_value_new_object(); for (int i = 0; i < root->size; i++) { jsmntok_t *token = POP(); @@ -687,6 +692,9 @@ tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, break; } case JSMN_ARRAY: { + if (depth >= SENTRY_JSON_MAX_DEPTH) { + goto error; + } rv = sentry_value_new_list(); for (int i = 0; i < root->size; i++) { sentry_value_t child; @@ -738,7 +746,7 @@ sentry__value_from_json(const char *buf, size_t buflen) sentry_value_t value_out; size_t tokens_consumed - = tokens_to_value(tokens, (size_t)token_count, buf, &value_out); + = tokens_to_value(tokens, (size_t)token_count, buf, 0, &value_out); sentry_free(tokens); if (tokens_consumed == (size_t)token_count) { diff --git a/src/sentry_value.c b/src/sentry_value.c index 05110ae87..2565fb71c 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1652,7 +1652,7 @@ sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) sentry_event_add_thread(event, thread); } -#define SENTRY_MPACK_MAX_DEPTH 32 +#define SENTRY_MPACK_MAX_DEPTH 64 static sentry_value_t value_from_mpack(mpack_node_t node, size_t depth, bool *ok) diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index d0d84f691..ab5e6a38b 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -41,6 +41,15 @@ write_nested_msgpack_arrays(char *buf, size_t depth) buf[depth] = (char)0xc0; // nil } +static void +write_nested_json_arrays(char *buf, size_t depth) +{ + memset(buf, '[', depth); + memcpy(buf + depth, "null", 4); + memset(buf + depth + 4, ']', depth); + buf[depth * 2 + 4] = '\0'; +} + SENTRY_TEST(value_null) { sentry_value_t val = sentry_value_new_null(); @@ -881,6 +890,21 @@ SENTRY_TEST(value_json_deeply_nested) sentry_value_decref(parsed); } +SENTRY_TEST(value_json_max_depth) +{ + char accepted[64 * 2 + sizeof("null")]; // SENTRY_JSON_MAX_DEPTH + write_nested_json_arrays(accepted, 64); + sentry_value_t value = sentry__value_from_json(accepted, strlen(accepted)); + TEST_CHECK(sentry_value_get_type(value) == SENTRY_VALUE_TYPE_LIST); + sentry_value_decref(value); + + char too_deep[65 * 2 + sizeof("null")]; // SENTRY_JSON_MAX_DEPTH + 1 + write_nested_json_arrays(too_deep, 65); + value = sentry__value_from_json(too_deep, strlen(too_deep)); + TEST_CHECK(sentry_value_is_null(value)); + sentry_value_decref(value); +} + SENTRY_TEST(value_json_len_out) { const char expected[] = "{\"message\":\"escaped\\nvalue\"}"; @@ -1576,14 +1600,14 @@ SENTRY_TEST(value_from_msgpack_list) SENTRY_TEST(value_from_msgpack_deeply_nested) { - char accepted[32 + 1]; // SENTRY_MPACK_MAX_DEPTH + 1 + char accepted[64 + 1]; // SENTRY_MPACK_MAX_DEPTH + 1 write_nested_msgpack_arrays(accepted, sizeof(accepted) - 1); sentry_value_t value = sentry__value_from_msgpack(accepted, sizeof(accepted)); TEST_CHECK(sentry_value_get_type(value) == SENTRY_VALUE_TYPE_LIST); sentry_value_decref(value); - char too_deep[32 + 2]; // SENTRY_MPACK_MAX_DEPTH + 2 + char too_deep[64 + 2]; // SENTRY_MPACK_MAX_DEPTH + 2 write_nested_msgpack_arrays(too_deep, sizeof(too_deep) - 1); value = sentry__value_from_msgpack(too_deep, sizeof(too_deep)); TEST_CHECK(sentry_value_is_null(value)); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 3529c6845..5b4599492 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -373,6 +373,7 @@ XX(value_json_escaping) XX(value_json_invalid_doubles) XX(value_json_len_out) XX(value_json_locales) +XX(value_json_max_depth) XX(value_json_parsing) XX(value_json_surrogates) XX(value_list)