From fb3e17c448ca1a8dbed83ca776401b059b92da7d Mon Sep 17 00:00:00 2001 From: Soma Aishwarya Date: Wed, 14 Oct 2026 21:17:28 +0530 Subject: [PATCH] fix(json): reject malformed surrogate escapes in cj5_get_str --- deps/cj5.c | 2 +- tests/check_cj5.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/deps/cj5.c b/deps/cj5.c index 22f629d59..dc0996fa7 100644 --- a/deps/cj5.c +++ b/deps/cj5.c @@ -688,7 +688,7 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index, error = CJ5_ERROR_INVALID; goto done; } - if(pos[1] != '\\' && pos[2] != 'u') { + if(pos[1] != '\\' || pos[2] != 'u') { error = CJ5_ERROR_INVALID; goto done; } diff --git a/tests/check_cj5.c b/tests/check_cj5.c index 7fa1b6700..91d57e2a4 100644 --- a/tests/check_cj5.c +++ b/tests/check_cj5.c @@ -316,6 +316,40 @@ START_TEST(getStrValidSurrogatePairStillWorks) { free(buf); } END_TEST +/* A high surrogate followed by a backslash that does not begin a "\u" escape is + * not a valid pair and must be rejected, not decoded. */ +START_TEST(getStrSurrogateBackslashWithoutUIsRejected) { + const char *json = "{'a':\"\\uD800\\q1234\"}"; + cj5_token tokens[16]; + cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 16, NULL); + ck_assert_int_eq(r.error, CJ5_ERROR_NONE); + + unsigned int idx = 2; + char *buf = (char*)malloc(tokens[idx].size + 1); + unsigned int outlen = 0; + cj5_error_code err = cj5_get_str(&r, idx, buf, &outlen); + ck_assert_int_eq(err, CJ5_ERROR_INVALID); + ck_assert_uint_eq(strlen(buf), outlen); + free(buf); +} END_TEST + +/* A high surrogate whose continuation has a 'u' in the second position but no + * leading backslash is likewise not a valid pair and must be rejected. */ +START_TEST(getStrSurrogateUWithoutBackslashIsRejected) { + const char *json = "{'a':\"\\uD800Xu1234\"}"; + cj5_token tokens[16]; + cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 16, NULL); + ck_assert_int_eq(r.error, CJ5_ERROR_NONE); + + unsigned int idx = 2; + char *buf = (char*)malloc(tokens[idx].size + 1); + unsigned int outlen = 0; + cj5_error_code err = cj5_get_str(&r, idx, buf, &outlen); + ck_assert_int_eq(err, CJ5_ERROR_INVALID); + ck_assert_uint_eq(strlen(buf), outlen); + free(buf); +} END_TEST + /* Additional coverage tests */ START_TEST(parseTokenOverflow) { @@ -574,6 +608,8 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_get_str, getStrTruncatedSurrogateIsTerminated); tcase_add_test(tc_get_str, getStrUnpairedLowSurrogateIsTerminated); tcase_add_test(tc_get_str, getStrSurrogateBadContinuationIsTerminated); + tcase_add_test(tc_get_str, getStrSurrogateBackslashWithoutUIsRejected); + tcase_add_test(tc_get_str, getStrSurrogateUWithoutBackslashIsRejected); tcase_add_test(tc_get_str, getStrWrongTokenTypeIsTerminated); tcase_add_test(tc_get_str, getStrValidSurrogatePairStillWorks);