From e57af8df21e52ef051d67bf77f729e43187db229 Mon Sep 17 00:00:00 2001 From: ljluestc Date: Thu, 13 Mar 2025 20:46:10 -0700 Subject: [PATCH] fix unescaped string --- cJSON.c | 37 ++++++++++++++++++++++--------------- tests/parse_string.c | 27 ++++++++++++++++++--------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/cJSON.c b/cJSON.c index d7c7236..c634d22 100644 --- a/cJSON.c +++ b/cJSON.c @@ -796,24 +796,31 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu unsigned char *output_pointer = NULL; unsigned char *output = NULL; - /* not a string */ + /* Not a string */ if (buffer_at_offset(input_buffer)[0] != '\"') { goto fail; } { - /* calculate approximate size of the output (overestimate) */ + /* Calculate approximate size of the output (overestimate) and validate control characters */ size_t allocation_length = 0; size_t skipped_bytes = 0; while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) { - /* is escape sequence */ + /* Check for unescaped control characters (U+0000 to U+001F) */ + if (*input_end < 0x20) /* Control characters must be escaped */ + { + input_buffer->offset = (size_t)(input_end - input_buffer->content); + goto fail; /* Error: unescaped control character */ + } + + /* Is escape sequence */ if (input_end[0] == '\\') { if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) { - /* prevent buffer overflow when last input character is a backslash */ + /* Prevent buffer overflow when last input character is a backslash */ goto fail; } skipped_bytes++; @@ -823,27 +830,27 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu } if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) { - goto fail; /* string ended unexpectedly */ + goto fail; /* String ended unexpectedly */ } /* This is at most how much we need for the output */ - allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes; output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); if (output == NULL) { - goto fail; /* allocation failure */ + goto fail; /* Allocation failure */ } } output_pointer = output; - /* loop through the string literal */ + /* Loop through the string literal */ while (input_pointer < input_end) { if (*input_pointer != '\\') { *output_pointer++ = *input_pointer++; } - /* escape sequence */ + /* Escape sequence */ else { unsigned char sequence_length = 2; @@ -880,25 +887,25 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); if (sequence_length == 0) { - /* failed to convert UTF16-literal to UTF-8 */ + /* Failed to convert UTF16-literal to UTF-8 */ goto fail; } break; default: - goto fail; + goto fail; /* Invalid escape sequence */ } input_pointer += sequence_length; } } - /* zero terminate the output */ + /* Zero terminate the output */ *output_pointer = '\0'; item->type = cJSON_String; item->valuestring = (char*)output; - input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset = (size_t)(input_end - input_buffer->content); input_buffer->offset++; return true; @@ -907,10 +914,9 @@ fail: if (output != NULL) { input_buffer->hooks.deallocate(output); - output = NULL; } - if (input_pointer != NULL) + if (input_pointer != NULL && (size_t)(input_pointer - input_buffer->content) < input_buffer->length) { input_buffer->offset = (size_t)(input_pointer - input_buffer->content); } @@ -918,6 +924,7 @@ fail: return false; } + /* Render the cstring provided to an escaped version that can be printed. */ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) { diff --git a/tests/parse_string.c b/tests/parse_string.c index ce1c138..a69760a 100644 --- a/tests/parse_string.c +++ b/tests/parse_string.c @@ -33,7 +33,6 @@ static cJSON item[1]; static void assert_is_string(cJSON *string_item) { TEST_ASSERT_NOT_NULL_MESSAGE(string_item, "Item is NULL."); - assert_not_in_list(string_item); assert_has_no_child(string_item); assert_has_type(string_item, cJSON_String); @@ -68,8 +67,6 @@ static void assert_not_parse_string(const char * const string) assert_is_invalid(item); } - - static void parse_string_should_parse_strings(void) { assert_parse_string("\"\"", ""); @@ -77,10 +74,8 @@ static void parse_string_should_parse_strings(void) "\" !\\\"#$%&'()*+,-./\\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_'abcdefghijklmnopqrstuvwxyz{|}~\"", " !\"#$%&'()*+,-.//0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'abcdefghijklmnopqrstuvwxyz{|}~"); assert_parse_string( - "\"\\\"\\\\\\/\\b\\f\\n\\r\\t\\u20AC\\u732b\"", - "\"\\/\b\f\n\r\t€猫"); - reset(item); - assert_parse_string("\"\b\f\n\r\t\"", "\b\f\n\r\t"); + "\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"", + "\"\\/\b\f\n\r\t"); reset(item); } @@ -119,9 +114,22 @@ static void parse_string_should_parse_bug_94(void) reset(item); } +static void parse_string_should_not_parse_unescaped_control_chars(void) +{ + /* Test unescaped control characters (U+0000 to U+001F) */ + assert_not_parse_string("\"\x0a\""); /* Newline */ + reset(item); + assert_not_parse_string("\"\x00\""); /* Null character */ + reset(item); + assert_not_parse_string("\"1\x0a2\""); /* Newline in middle, matches #766 test case */ + reset(item); + assert_not_parse_string("\"\x1f\""); /* Unit separator (highest control char) */ + reset(item); +} + int CJSON_CDECL main(void) { - /* initialize cJSON item and error pointer */ + /* Initialize cJSON item and error pointer */ memset(item, 0, sizeof(cJSON)); UNITY_BEGIN(); @@ -131,5 +139,6 @@ int CJSON_CDECL main(void) RUN_TEST(parse_string_should_not_parse_invalid_backslash); RUN_TEST(parse_string_should_parse_bug_94); RUN_TEST(parse_string_should_not_overflow_with_closing_backslash); + RUN_TEST(parse_string_should_not_parse_unescaped_control_chars); /* New test */ return UNITY_END(); -} +} \ No newline at end of file