From 10d305136ce1d5690680995972fa13fcf63e1288 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:30 -0700 Subject: [PATCH 1/4] F-6259 - https://fenrir.wolfssl.com/finding/6259 - Print doubles with 17 significant digits so they round-trip exactly --- src/json/centijson_sax.c | 6 +---- tests/unittests.c | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 1c71d53..842ebe7 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -1315,11 +1315,7 @@ json_dump_uint64(uint64_t u64, JSON_DUMP_CALLBACK write_func, void* user_data) WOLFSENTRY_API int json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) { -#if defined(FREERTOS) || (__STDC_VERSION__ < 199901L) - static const char fmt[] = "%.16f"; -#else - static const char fmt[] = "%.16lg"; -#endif + static const char fmt[] = "%.17g"; static const size_t extra_bytes = 2; /* Space reserved for ".0" */ #ifdef CENTIJSON_USE_LOCALE struct lconv* locale_info; diff --git a/tests/unittests.c b/tests/unittests.c index 7a75686..075d140 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5466,6 +5466,56 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_RETURN_OK; } +struct sax_dump_capture { + char text[128]; + size_t len; +}; + +static int sax_dump_capture_cb(const unsigned char *str, size_t size, void *user_data) { + struct sax_dump_capture *cap = (struct sax_dump_capture *)user_data; + if (size >= sizeof cap->text - cap->len) + return -1; + memcpy(cap->text + cap->len, str, size); + cap->len += size; + cap->text[cap->len] = '\0'; + return 0; +} + +static int sax_dump_double_text(double dbl, struct sax_dump_capture *cap) { + cap->len = 0; + cap->text[0] = '\0'; + return json_dump_double(dbl, sax_dump_capture_cb, cap); +} + +static wolfsentry_errcode_t test_json_sax_double_roundtrip(void) { + static const double corpus[] = { + 1.0 + 2.220446049250313e-16, + 0.1 + 0.2, + 0.1, + 1.0 / 3.0, + -2.0 / 3.0, + 1.7976931348623157e308, + 2.2250738585072014e-308, + 4.9406564584124654e-324, + 1e-300, + 123456789.12345679 + }; + struct sax_dump_capture cap; + size_t i; + double reparsed; + + for (i = 0; i < sizeof corpus / sizeof corpus[0]; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(corpus[i], &cap)); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + if (memcmp(&reparsed, &corpus[i], sizeof reparsed) != 0) { + (void)fprintf(stderr, "double did not survive dump and reparse: %s\n", cap.text); + return 1; + } + } + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -5784,6 +5834,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json failed for " TEST_NUMERIC_JSON_CONFIG_PATH ", " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_double_roundtrip(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_double_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 6dda5879b54832128351cd16fd60c3ce249962ec Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:38 -0700 Subject: [PATCH 2/4] F-8756 - https://fenrir.wolfssl.com/finding/8756 - Dump non-finite doubles as null instead of an invalid token --- src/json/centijson_sax.c | 5 +++++ tests/unittests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 842ebe7..b300999 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -54,6 +54,7 @@ #ifdef CENTIJSON_USE_LOCALE #include /* localeconv() */ #endif +#include /* DBL_MAX */ #include /* snprintf() */ #include #include @@ -1330,6 +1331,10 @@ json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) char* fp; int ret; + /* JSON has no representation for infinity or NaN. */ + if(dbl != dbl || dbl > DBL_MAX || dbl < -DBL_MAX) + return write_func((const unsigned char *)"null", 4, user_data); + while(1) { n = snprintf(buffer, capacity, fmt, dbl); if(n >= 0 && (size_t)n < capacity) diff --git a/tests/unittests.c b/tests/unittests.c index 075d140..eb6f05a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5516,6 +5516,32 @@ static wolfsentry_errcode_t test_json_sax_double_roundtrip(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { + double inf; + double reparsed; + struct sax_dump_capture cap; + + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"1e999", 5, &inf)); + WOLFSENTRY_EXIT_ON_FALSE(inf > 1.7976931348623157e308); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(-inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"nan", 3, &reparsed)); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(reparsed, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(1.0, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "1.0") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + WOLFSENTRY_EXIT_ON_FALSE(reparsed == 1.0); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -5840,6 +5866,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_sax_double_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_nonfinite_dump(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_nonfinite_dump failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 542ca1df7b9ccd0f92a99d8020c416804ff3c3e1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:38 -0700 Subject: [PATCH 3/4] F-10508 - https://fenrir.wolfssl.com/finding/10508 - Reject incomplete true, false and null literals at end of input --- src/json/centijson_sax.c | 13 +++++-- tests/unittests.c | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index b300999..6e68187 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -405,8 +405,12 @@ json_literal_automaton(JSON_PARSER* parser, const unsigned char* input, size_t s if(off < size && !IS_TOKEN_BOUNDARY(input[off])) { memcpy(&parser->pos, &parser->value_pos, sizeof(JSON_INPUT_POS)); json_raise(parser, JSON_ERR_SYNTAX); - } else if(input == NULL /* EOF */ || - (off < size && IS_TOKEN_BOUNDARY(input[off]))) { + } else if(input == NULL /* EOF */) { + if(parser->substate < literal_size) + json_raise_for_value(parser, JSON_ERR_SYNTAX); + else + json_process(parser, type, NULL, 0); + } else if(off < size && IS_TOKEN_BOUNDARY(input[off])) { json_process(parser, type, NULL, 0); } return off; @@ -981,7 +985,10 @@ json_fini(JSON_PARSER* parser, JSON_INPUT_POS* p_pos) if(parser->automaton != AUTOMATON_MAIN) { json_raise(parser, JSON_ERR_SYNTAX); } - } else if(parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF)) { + } + + if(parser->errcode >= 0 && + (parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF))) { json_raise_unexpected(parser); } } diff --git a/tests/unittests.c b/tests/unittests.c index eb6f05a..67fe42a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5481,6 +5481,14 @@ static int sax_dump_capture_cb(const unsigned char *str, size_t size, void *user return 0; } +static int sax_noop_process_cb(JSON_TYPE type, const unsigned char *data, size_t data_size, void *user_data) { + (void)type; + (void)data; + (void)data_size; + (void)user_data; + return 0; +} + static int sax_dump_double_text(double dbl, struct sax_dump_capture *cap) { cap->len = 0; cap->text[0] = '\0'; @@ -5542,6 +5550,70 @@ static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_truncated_input(void) { + static const JSON_CALLBACKS callbacks = { sax_noop_process_cb }; + static const JSON_CONFIG sax_config = { + 4096, /* max_total_len */ + 1000, /* max_total_values */ + 20, /* max_number_len */ + 256, /* max_string_len */ + 64, /* max_key_len */ + 10, /* max_nesting_level */ + 0 /* flags */ + }; + static const char * const truncated[] = { + "t", "tr", "tru", + "f", "fa", "fal", "fals", + "n", "nu", "nul", + "[t", "[fals", "[nul", + "{\"a\":t", "{\"a\":tru", "{\"a\":nu", + "[true", "[false", "[null", "[1", "{\"a\":true", "{\"a\":1", "{\"a\":[null" + }; + static const char * const complete[] = { + "true", "false", "null", "[true]", "[null, false]", "{\"a\":true}" + }; + struct wolfsentry_context *wolfsentry; + size_t i; + JSON_INPUT_POS pos; + int ret; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (i = 0; i < sizeof truncated / sizeof truncated[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)truncated[i], strlen(truncated[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret >= 0) { + (void)fprintf(stderr, "truncated JSON accepted: %s\n", truncated[i]); + return 1; + } + } + + for (i = 0; i < sizeof complete / sizeof complete[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)complete[i], strlen(complete[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret < 0) { + (void)fprintf(stderr, "complete JSON rejected: %s\n", complete[i]); + return 1; + } + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -5872,6 +5944,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_sax_nonfinite_dump failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_truncated_input(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_truncated_input failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From f825a3b6b479a2333cbb74eb36a16b7f7c3a5327 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:39 -0700 Subject: [PATCH 4/4] F-8772 - https://fenrir.wolfssl.com/finding/8772 - Accept double text that exactly fills the local conversion buffer --- src/json/centijson_sax.c | 2 +- tests/unittests.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 6e68187..0bb42ba 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -1229,7 +1229,7 @@ json_number_to_double(const unsigned char* num, size_t num_size, double* p_resul * requirements. */ - if(num_size + 1 < sizeof(local_buffer)) { + if(num_size < sizeof(local_buffer)) { /* The number is short enough so we can avoid heap allocation. */ buffer = local_buffer; } else { diff --git a/tests/unittests.c b/tests/unittests.c index 67fe42a..59da584 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5550,6 +5550,25 @@ static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_number_text_boundary(void) { + unsigned char num[65]; + double result; + size_t len; + + memset(num, '0', sizeof num); + num[0] = '1'; + + for (len = 62; len <= 63; ++len) { + result = 0.0; + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double(num, len, &result)); + WOLFSENTRY_EXIT_ON_FALSE(result == ((len == 62) ? 1e61 : 1e62)); + } + + WOLFSENTRY_EXIT_ON_TRUE(json_number_to_double(num, 64, &result) >= 0); + + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_json_sax_truncated_input(void) { static const JSON_CALLBACKS callbacks = { sax_noop_process_cb }; static const JSON_CONFIG sax_config = { @@ -5945,6 +5964,12 @@ int main(int argc, char* argv[]) { err = 1; } + ret = test_json_sax_number_text_boundary(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_number_text_boundary failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_sax_truncated_input(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_sax_truncated_input failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret));