From 90a0184062f5312bfd60a57fba401f6203e5408b Mon Sep 17 00:00:00 2001 From: EibzChan Date: Fri, 14 Jul 2023 06:24:35 +0000 Subject: [PATCH] =?UTF-8?q?backport=20patch=20to=20fix=20json=5Ftokener=5F?= =?UTF-8?q?parse=5Fex=EF=BC=9Ahandle=20out=20of=20memory=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: EibzChan --- ...parse_ex-handle-out-of-memory-errors.patch | 561 ++++++++++++++++++ json-c.spec | 6 +- 2 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch diff --git a/backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch b/backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch new file mode 100644 index 0000000..a9d3d66 --- /dev/null +++ b/backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch @@ -0,0 +1,561 @@ +From 9e6acc9a4eefe9f092aa3a3890a6e5c6ca2d5ed1 Mon Sep 17 00:00:00 2001 +From: Tobias Stoeckmann +Date: Sun, 20 Mar 2022 17:22:07 +0100 +Subject: [PATCH] json_tokener_parse_ex: handle out of memory errors + +Do not silently truncate values or skip entries if out of memory errors +occur. + +Proof of Concept: + +- Create poc.c, a program which creates an eight megabyte large json + object with key "A" and a lot of "B"s as value, one of them is + UTF-formatted: + +```c + #include + #include + #include + + #include "json.h" + + #define STR_LEN (8 * 1024 * 1024) + #define STR_PREFIX "{ \"A\": \"" + #define STR_SUFFIX "\\u0042\" }" + +int main(void) { + char *str; + struct json_tokener *tok; + struct json_object *obj; + + if ((tok = json_tokener_new()) == NULL) + errx(1, "json_tokener_new"); + + if ((str = malloc(STR_LEN)) == NULL) + err(1, "malloc"); + memset(str, 'B', STR_LEN); + memcpy(str, STR_PREFIX, sizeof(STR_PREFIX) - 1); + memcpy(str + STR_LEN - sizeof(STR_SUFFIX), STR_SUFFIX, sizeof(STR_SUFFIX)); + + obj = json_tokener_parse(str); + free(str); + + printf("%p\n", obj); + if (obj != NULL) { + printf("%.*s\n", 50, json_object_to_json_string(obj)); + json_object_put(obj); + } + + json_tokener_free(tok); + return 0; +} +``` +- Compile and run poc, assuming you have enough free heap space: +``` +gcc $(pkg-config --cflags --libs) -o poc poc.c +./poc +0x559421e15de0 +{ "A": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +``` +- Reduce available heap and run again, which leads to truncation: +``` +ulimit -d 10000 +./poc +0x555a5b453de0 +{ "A": "B" } +``` +- Compile json-c with this change and run with reduced heap again: +``` +ulimit -d 10000 +./poc +(nil) +``` + +The output is limited to 70 characters, i.e. json-c parses the 8 MB +string correctly but the poc does not print all of them to the screen. + +The truncation occurs because the parser tries to add all chars up +to the UTF-8 formatted 'B' at once. Since memory is limited to 10 MB +there is not enough for this operation. The parser does not fail but +continues normally. + +Another possibility is to create a json file close to 2 GB and run a +program on a system with limited amount of RAM, i.e. around 3 GB. But +ulimit restrictions are much easier for proof of concepts. + +Treat memory errors correctly and abort operations. + +Conflict:NA +Reference:https://github.com/json-c/json-c/commit/e9d3ab209a8f774b476c8933f18a85bf9b611c16 +--- + json_tokener.c | 145 ++++++++++++++++++++++++++++++++++--------------- + json_tokener.h | 1 + + 2 files changed, 103 insertions(+), 43 deletions(-) + +diff --git a/json_tokener.c b/json_tokener.c +index 0c09b66e8d..af03a85780 100644 +--- a/json_tokener.c ++++ b/json_tokener.c +@@ -103,6 +103,7 @@ static const char *json_tokener_errors[] = { + "success", + "continue", + "nesting too deep", ++ "out of memory", + "unexpected end of data", + "unexpected character", + "null expected", +@@ -284,11 +285,24 @@ struct json_object *json_tokener_parse_verbose(const char *str, enum json_tokene + + /* ADVANCE_CHAR() macro: + * Increments str & tok->char_offset. +- * For convenience of existing conditionals, returns the old value of c (0 on eof) ++ * For convenience of existing conditionals, returns the old value of c (0 on eof). + * Implicit inputs: c var + */ + #define ADVANCE_CHAR(str, tok) (++(str), ((tok)->char_offset)++, c) + ++/* printbuf_memappend_checked(p, s, l) macro: ++ * Add string s of length l to printbuffer p. ++ * If operation fails abort parse operation with memory error. ++ */ ++#define printbuf_memappend_checked(p, s, l) \ ++ do { \ ++ if (printbuf_memappend((p), (s), (l)) < 0) \ ++ { \ ++ tok->err = json_tokener_error_memory; \ ++ goto out; \ ++ } \ ++ } while (0) ++ + /* End optimization macro defs */ + + struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *str, int len) +@@ -336,7 +350,11 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + char *tmplocale; + tmplocale = setlocale(LC_NUMERIC, NULL); + if (tmplocale) ++ { + oldlocale = strdup(tmplocale); ++ if (oldlocale == NULL) ++ return NULL; ++ } + setlocale(LC_NUMERIC, "C"); + } + #endif +@@ -358,7 +376,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + if (c == '/' && !(tok->flags & JSON_TOKENER_STRICT)) + { + printbuf_reset(tok->pb); +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + state = json_tokener_state_comment_start; + } + else +@@ -376,14 +394,20 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + saved_state = json_tokener_state_object_field_start; + current = json_object_new_object(); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + break; + case '[': + state = json_tokener_state_eatws; + saved_state = json_tokener_state_array; + current = json_object_new_array(); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + break; + case 'I': + case 'i': +@@ -486,7 +510,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + } + current = json_object_new_double(is_negative ? -INFINITY : INFINITY); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; +@@ -496,7 +523,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + int size; + int size_nan; +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + size = json_min(tok->st_pos + 1, json_null_str_len); + size_nan = json_min(tok->st_pos + 1, json_nan_str_len); + if ((!(tok->flags & JSON_TOKENER_STRICT) && +@@ -519,7 +546,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + current = json_object_new_double(NAN); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; +@@ -548,7 +578,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + tok->err = json_tokener_error_parse_comment; + goto out; + } +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + break; + + case json_tokener_state_comment: +@@ -559,12 +589,12 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + goto out; + } + } +- printbuf_memappend_fast(tok->pb, case_start, 1 + str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, 1 + str - case_start); + state = json_tokener_state_comment_end; + } + break; +@@ -577,19 +607,19 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + goto out; + } + } +- printbuf_memappend_fast(tok->pb, case_start, str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, str - case_start); + MC_DEBUG("json_tokener_comment: %s\n", tok->pb->buf); + state = json_tokener_state_eatws; + } + break; + + case json_tokener_state_comment_end: +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + if (c == '/') + { + MC_DEBUG("json_tokener_comment: %s\n", tok->pb->buf); +@@ -609,28 +639,31 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + if (c == tok->quote_char) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + current = + json_object_new_string_len(tok->pb->buf, tok->pb->bpos); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + break; + } + else if (c == '\\') + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + saved_state = json_tokener_state_string; + state = json_tokener_state_string_escape; + break; + } + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + goto out; + } + } +@@ -643,7 +676,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + case '"': + case '\\': + case '/': +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + state = saved_state; + break; + case 'b': +@@ -652,15 +685,15 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + case 't': + case 'f': + if (c == 'b') +- printbuf_memappend_fast(tok->pb, "\b", 1); ++ printbuf_memappend_checked(tok->pb, "\b", 1); + else if (c == 'n') +- printbuf_memappend_fast(tok->pb, "\n", 1); ++ printbuf_memappend_checked(tok->pb, "\n", 1); + else if (c == 'r') +- printbuf_memappend_fast(tok->pb, "\r", 1); ++ printbuf_memappend_checked(tok->pb, "\r", 1); + else if (c == 't') +- printbuf_memappend_fast(tok->pb, "\t", 1); ++ printbuf_memappend_checked(tok->pb, "\t", 1); + else if (c == 'f') +- printbuf_memappend_fast(tok->pb, "\f", 1); ++ printbuf_memappend_checked(tok->pb, "\f", 1); + state = saved_state; + break; + case 'u': +@@ -720,8 +753,8 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + /* High surrogate was not followed by a low surrogate + * Replace the high and process the rest normally + */ +- printbuf_memappend_fast(tok->pb, +- (char *)utf8_replacement_char, 3); ++ printbuf_memappend_checked(tok->pb, ++ (char *)utf8_replacement_char, 3); + } + tok->high_surrogate = 0; + } +@@ -730,14 +763,14 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + unsigned char unescaped_utf[1]; + unescaped_utf[0] = tok->ucs_char; +- printbuf_memappend_fast(tok->pb, (char *)unescaped_utf, 1); ++ printbuf_memappend_checked(tok->pb, (char *)unescaped_utf, 1); + } + else if (tok->ucs_char < 0x800) + { + unsigned char unescaped_utf[2]; + unescaped_utf[0] = 0xc0 | (tok->ucs_char >> 6); + unescaped_utf[1] = 0x80 | (tok->ucs_char & 0x3f); +- printbuf_memappend_fast(tok->pb, (char *)unescaped_utf, 2); ++ printbuf_memappend_checked(tok->pb, (char *)unescaped_utf, 2); + } + else if (IS_HIGH_SURROGATE(tok->ucs_char)) + { +@@ -763,7 +796,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + else if (IS_LOW_SURROGATE(tok->ucs_char)) + { + /* Got a low surrogate not preceded by a high */ +- printbuf_memappend_fast(tok->pb, (char *)utf8_replacement_char, 3); ++ printbuf_memappend_checked(tok->pb, (char *)utf8_replacement_char, 3); + } + else if (tok->ucs_char < 0x10000) + { +@@ -771,7 +804,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + unescaped_utf[0] = 0xe0 | (tok->ucs_char >> 12); + unescaped_utf[1] = 0x80 | ((tok->ucs_char >> 6) & 0x3f); + unescaped_utf[2] = 0x80 | (tok->ucs_char & 0x3f); +- printbuf_memappend_fast(tok->pb, (char *)unescaped_utf, 3); ++ printbuf_memappend_checked(tok->pb, (char *)unescaped_utf, 3); + } + else if (tok->ucs_char < 0x110000) + { +@@ -780,12 +813,12 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + unescaped_utf[1] = 0x80 | ((tok->ucs_char >> 12) & 0x3f); + unescaped_utf[2] = 0x80 | ((tok->ucs_char >> 6) & 0x3f); + unescaped_utf[3] = 0x80 | (tok->ucs_char & 0x3f); +- printbuf_memappend_fast(tok->pb, (char *)unescaped_utf, 4); ++ printbuf_memappend_checked(tok->pb, (char *)unescaped_utf, 4); + } + else + { + /* Don't know what we got--insert the replacement char */ +- printbuf_memappend_fast(tok->pb, (char *)utf8_replacement_char, 3); ++ printbuf_memappend_checked(tok->pb, (char *)utf8_replacement_char, 3); + } + state = saved_state; // i.e. _state_string or _state_object_field + } +@@ -800,7 +833,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + * it. Put a replacement char in for the high surrogate + * and pop back up to _state_string or _state_object_field. + */ +- printbuf_memappend_fast(tok->pb, (char *)utf8_replacement_char, 3); ++ printbuf_memappend_checked(tok->pb, (char *)utf8_replacement_char, 3); + tok->high_surrogate = 0; + tok->ucs_char = 0; + tok->st_pos = 0; +@@ -819,7 +852,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + * Put a replacement char in for the high surrogate + * and handle the escape sequence normally. + */ +- printbuf_memappend_fast(tok->pb, (char *)utf8_replacement_char, 3); ++ printbuf_memappend_checked(tok->pb, (char *)utf8_replacement_char, 3); + tok->high_surrogate = 0; + tok->ucs_char = 0; + tok->st_pos = 0; +@@ -834,7 +867,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + case json_tokener_state_boolean: + { + int size1, size2; +- printbuf_memappend_fast(tok->pb, &c, 1); ++ printbuf_memappend_checked(tok->pb, &c, 1); + size1 = json_min(tok->st_pos + 1, json_true_str_len); + size2 = json_min(tok->st_pos + 1, json_false_str_len); + if ((!(tok->flags & JSON_TOKENER_STRICT) && +@@ -845,7 +878,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + current = json_object_new_boolean(1); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; +@@ -859,7 +895,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + current = json_object_new_boolean(0); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; +@@ -939,7 +978,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) + { +- printbuf_memappend_fast(tok->pb, case_start, case_len); ++ printbuf_memappend_checked(tok->pb, case_start, case_len); + goto out; + } + } +@@ -948,7 +987,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + it might have been intended to be, and return a potentially + more understandable error right away. + However, if we're at the top-level, use the number as-is +- because c can be part of a new object to parse on the ++ because c can be part of a new object to parse on the + next call to json_tokener_parse(). + */ + if (tok->depth > 0 && c != ',' && c != ']' && c != '}' && c != '/' && +@@ -958,7 +997,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + goto out; + } + if (case_len > 0) +- printbuf_memappend_fast(tok->pb, case_start, case_len); ++ printbuf_memappend_checked(tok->pb, case_start, case_len); + + // Check for -Infinity + if (tok->pb->buf[0] == '-' && case_len <= 1 && (c == 'i' || c == 'I')) +@@ -993,7 +1032,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + current = json_object_new_int64(num64); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + } + else if (!tok->is_double && tok->pb->buf[0] != '-' && + json_parse_uint64(tok->pb->buf, &numuint64) == 0) +@@ -1009,13 +1051,19 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + num64 = (uint64_t)numuint64; + current = json_object_new_int64(num64); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + } + else + { + current = json_object_new_uint64(numuint64); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + } + } + else if (tok->is_double && +@@ -1024,7 +1072,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + current = json_object_new_double_s(numd, tok->pb->buf); + if (current == NULL) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + } + else + { +@@ -1069,7 +1120,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + + case json_tokener_state_array_add: + if (json_object_array_add(current, obj) != 0) ++ { ++ tok->err = json_tokener_error_memory; + goto out; ++ } + saved_state = json_tokener_state_array_sep; + state = json_tokener_state_eatws; + goto redo_char; +@@ -1129,25 +1183,30 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * + { + if (c == tok->quote_char) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + obj_field_name = strdup(tok->pb->buf); ++ if (obj_field_name == NULL) ++ { ++ tok->err = json_tokener_error_memory; ++ goto out; ++ } + saved_state = json_tokener_state_object_field_end; + state = json_tokener_state_eatws; + break; + } + else if (c == '\\') + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + saved_state = json_tokener_state_object_field; + state = json_tokener_state_string_escape; + break; + } + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) + { +- printbuf_memappend_fast(tok->pb, case_start, +- str - case_start); ++ printbuf_memappend_checked(tok->pb, case_start, ++ str - case_start); + goto out; + } + } +diff --git a/json_tokener.h b/json_tokener.h +index a07e12ce7e..c1502e8294 100644 +--- a/json_tokener.h ++++ b/json_tokener.h +@@ -28,6 +28,7 @@ enum json_tokener_error + json_tokener_success, + json_tokener_continue, + json_tokener_error_depth, ++ json_tokener_error_memory, + json_tokener_error_parse_eof, + json_tokener_error_parse_unexpected, + json_tokener_error_parse_null, diff --git a/json-c.spec b/json-c.spec index 0014398..975586c 100644 --- a/json-c.spec +++ b/json-c.spec @@ -6,7 +6,7 @@ Name: json-c Version: 0.16 -Release: 3 +Release: 4 Summary: JSON implementation in C License: MIT @@ -19,6 +19,7 @@ Patch6001: backport-Add-test-to-check-for-the-memory-leak-mentioned-in-issu Patch6002: backport-Fix-memory-leak-with-emtpy-strings-in-json_object_set_string.patch Patch6003: backport-json_object_from_fd_ex-fail-if-file-is-too-large.patch Patch6004: backport-Explicitly-check-for-integer-overflow-when-parsing.patch +Patch6005: backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch %description JSON-C implements a reference counting object model that allows you @@ -106,6 +107,9 @@ end %doc %{_pkgdocdir} %changelog +* Fri Jul 14 2023 EibzChan - 0.16-4 +- add backport-json_tokener_parse_ex-handle-out-of-memory-errors.patch + * Fri Apr 21 2023 zhangrui - 0.16-3 - backport patch to fix integer overflow -- Gitee