From b623071a2a98ef52bb5d273fc3afa9fd3ddee3ca Mon Sep 17 00:00:00 2001 From: David Steele Date: Mon, 12 Oct 2026 10:10:20 +0200 Subject: [PATCH] Fix reads past the end of JSON strings ending in an escape. jsonReadSkipStr() stepped past the terminating null when a backslash was the last character. It also advanced one character too far after a \uXXXX escape, which skipped the closing quote when the escape was the last character of the string and stepped past the terminating null when the escape was at the end of the input. jsonReadStr() did not check that both digits of a \u00XX escape were present, so a truncated escape at the end of the input was read past the terminating null. The skip path validates the JSON values in info files and manifests. The read path reads object store responses. --- src/common/type/json.c | 12 ++++++++++-- test/src/module/common/type/jsonTest.c | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/common/type/json.c b/src/common/type/json.c index 53b18b32e..e8af31d6b 100644 --- a/src/common/type/json.c +++ b/src/common/type/json.c @@ -838,11 +838,16 @@ jsonReadSkipStr(JsonRead *const this) if (digitIdx != 4) THROW_FMT(JsonFormatError, "unable to decode at: %s", this->json - 2); - this->json += 4; + // Move to the last digit + this->json += 3; break; } + // End of string after the escape + case '\0': + THROW(JsonFormatError, "expected '\"' but found null delimiter"); + // Any other escape default: break; @@ -1026,8 +1031,11 @@ jsonReadStr(JsonRead *const this) this->json++; // We don't know how to decode anything except ASCII so fail if it looks like Unicode - if (strncmp(this->json, "00", 2) != 0) + if (strncmp(this->json, "00", 2) != 0 || !isxdigit((unsigned char)this->json[2]) || + !isxdigit((unsigned char)this->json[3])) + { THROW_FMT(JsonFormatError, "unable to decode at: %s", this->json - 2); + } // Decode char this->json += 2; diff --git a/test/src/module/common/type/jsonTest.c b/test/src/module/common/type/jsonTest.c index 1c07900cf..06fc314a1 100644 --- a/test/src/module/common/type/jsonTest.c +++ b/test/src/module/common/type/jsonTest.c @@ -60,6 +60,8 @@ testRun(void) TEST_TITLE("error on invalid ascii encoding"); TEST_ERROR(jsonReadStr(jsonReadNew(STRDEF("\"\\u1111"))), JsonFormatError, "unable to decode at: \\u1111"); + TEST_ERROR(jsonReadStr(jsonReadNew(STRDEF("\"\\u00x1"))), JsonFormatError, "unable to decode at: \\u00x1"); + TEST_ERROR(jsonReadStr(jsonReadNew(STRDEF("\"\\u00a"))), JsonFormatError, "unable to decode at: \\u00a"); // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("error on maximum depth"); @@ -229,8 +231,11 @@ testRun(void) TEST_TITLE("validate"); TEST_RESULT_VOID(jsonValidate(json), "valid"); + TEST_RESULT_VOID(jsonValidate(STRDEF("[\"\\u0041\",\"\\u1234\"]")), "valid escapes before end of string"); + TEST_ERROR(jsonValidate(STRDEF("\"\\u0041")), JsonFormatError, "expected '\"' but found null delimiter"); TEST_ERROR(jsonValidate(STRDEF("\"\\u000")), JsonFormatError, "unable to decode at: \\u000"); TEST_ERROR(jsonValidate(STRDEF("\"")), JsonFormatError, "expected '\"' but found null delimiter"); + TEST_ERROR(jsonValidate(STRDEF("\"\\")), JsonFormatError, "expected '\"' but found null delimiter"); TEST_ERROR(jsonValidate(STRDEF("{\"key\"x")), JsonFormatError, "expected : after key at: x"); TEST_ERROR(jsonValidate(strNewFmt("%s]", strZ(json))), JsonFormatError, "characters after JSON at: ]"); }