Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -991,18 +991,40 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe
case '\r':
case '\t':
/* one character escape sequence */
if (escape_characters == (size_t)-1)
{
/* counter would wrap and under-allocate below */
return false;
}
escape_characters++;
break;
default:
if (*input_pointer < 32)
{
/* UTF-16 escape sequence uXXXX */
if (escape_characters > ((size_t)-1 - 5))
{
/* counter would wrap and under-allocate below */
return false;
}
escape_characters += 5;
}
break;
}
}
output_length = (size_t)(input_pointer - input) + escape_characters;
{
size_t input_length = (size_t)(input_pointer - input);
/* The allocation below adds the quotes; if any of these sums
* wrapped (32-bit size_t past ~4G, CVE-2026-16554), ensure()
* would hand out a small buffer while the write loop emits
* the true length. Fail instead. */
if ((escape_characters > ((size_t)-1 - sizeof("\"\""))) ||
(input_length > ((size_t)-1 - sizeof("\"\"") - escape_characters)))
{
return false;
}
output_length = input_length + escape_characters;
}

output = ensure(output_buffer, output_length + sizeof("\"\""));
if (output == NULL)
Expand Down
31 changes: 31 additions & 0 deletions tests/print_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,36 @@ static void print_string_should_print_utf8(void)
assert_print_string("\"ü猫慕\"", "ü猫慕");
}

static void print_string_should_print_escape_heavy_strings_exactly(void)
{
/* CVE-2026-16554 regression guard: escape counting must stay exact
* at scale so the allocation always matches the emitted length. */
char input[101];
char expected[701];
unsigned char printed[1024];
printbuffer buffer = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
size_t i = 0;

memset(input, 1, 100);
input[100] = '\0';
expected[0] = '"';
for (i = 0; i < 100; i++)
{
memcpy(expected + 1 + 6 * i, "\\u0001", 6);
}
expected[601] = '"';
expected[602] = '\0';

buffer.buffer = printed;
buffer.length = sizeof(printed);
buffer.offset = 0;
buffer.noalloc = true;
buffer.hooks = global_hooks;

TEST_ASSERT_TRUE_MESSAGE(print_string_ptr((const unsigned char*)input, &buffer), "Failed to print string.");
TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, printed, "Escape-heavy output isn't exact.");
}

int CJSON_CDECL main(void)
{
/* initialize cJSON item */
Expand All @@ -73,6 +103,7 @@ int CJSON_CDECL main(void)
RUN_TEST(print_string_should_print_empty_strings);
RUN_TEST(print_string_should_print_ascii);
RUN_TEST(print_string_should_print_utf8);
RUN_TEST(print_string_should_print_escape_heavy_strings_exactly);

return UNITY_END();
}