From d316ba6bf8921327dfb36ef8e144424692991354 Mon Sep 17 00:00:00 2001 From: yuef07 Date: Tue, 8 Sep 2026 14:32:12 +0300 Subject: [PATCH] fix: report stackLimit exceedance as a parse error, not a thrown exception OurReader::readValue() enforced the configured stackLimit by calling throwRuntimeError() when JSON_USE_EXCEPTION is enabled (the default). The Json::RuntimeError escapes CharReader::parse(), whose contract is to return false and fill in the error string, so a document with ~1100 nested '[' characters terminates an application that does not catch the exception. Report the depth failure through addError() instead, mirroring the existing JSON_USE_EXCEPTION=0 path, and update the CharReaderBuilder documentation accordingly. Fixes #1704 --- include/json/reader.h | 4 +-- src/lib_json/json_reader.cpp | 9 +++---- src/test_lib_json/main.cpp | 52 +++++++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/include/json/reader.h b/include/json/reader.h index d4db75a4d..9fb0dc1fa 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -339,8 +339,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * - `"allowSingleQuotes": false or true` * - true if '' are allowed for strings (both keys and values) * - `"stackLimit": integer` - * - Exceeding stackLimit (recursive depth of `readValue()`) will cause an - * exception. + * - Exceeding stackLimit (recursive depth of `readValue()`) makes the + * parse fail with an error message; no exception is thrown. * - This is a security issue (seg-faults caused by deeply nested JSON), so * the default is low. * - `"failIfExtra": false or true` diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 2f901f510..6ea588a3d 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -1050,16 +1050,15 @@ bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool OurReader::readValue() { Token token; if (nodes_.size() > features_.stackLimit_) { -#if JSON_USE_EXCEPTION - throwRuntimeError("Exceeded stackLimit in readValue()."); -#else - // throwRuntimeError aborts. Don't abort here. + // Exceeding the nesting limit is a parse failure, not a crash: report it + // through the error collection instead of throwing, so that parsing + // overly deep input returns false with a message rather than aborting + // the process when the exception is not caught. token.start_ = current_; token.end_ = current_; token.type_ = tokenError; return addError( "Exceeded stackLimit for nested object and/or array values.", token); -#endif } readTokenSkippingComments(token); bool successful = true; diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index e09b87b84..9b79206db 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -3554,7 +3554,6 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { Json::CharReaderBuilder b; Json::Value root; -#if JSON_USE_EXCEPTION char const doc[] = R"({ "property" : "value" })"; { b.settings_["stackLimit"] = 2; @@ -3569,20 +3568,12 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { b.settings_["stackLimit"] = 1; CharReaderPtr reader(b.newCharReader()); Json::String errs; - JSONTEST_ASSERT_THROWS( - reader->parse(doc, doc + std::strlen(doc), &root, &errs)); - } - // Default stack limit should reject deeply nested input (regression test for - // stack exhaustion from fuzz input like [[[[...]]]]) - { - Json::CharReaderBuilder defaultBuilder; - Json::String nested(300, '['); - CharReaderPtr reader(defaultBuilder.newCharReader()); - Json::String errs; - JSONTEST_ASSERT_THROWS(reader->parse( - nested.data(), nested.data() + nested.size(), &root, &errs)); + JSONTEST_ASSERT(!reader->parse(doc, doc + std::strlen(doc), &root, &errs)); + JSONTEST_ASSERT( + errs == + "* Line 1, Column 15\n" + " Exceeded stackLimit for nested object and/or array values.\n"); } -#else b.settings_["stackLimit"] = 10; CharReaderPtr reader(b.newCharReader()); { @@ -3612,7 +3603,38 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { JSONTEST_ASSERT(reader->parse( onLimit.data(), onLimit.data() + onLimit.size(), &root, &errs)); } -#endif // JSON_USE_EXCEPTION + // Default stack limit should reject deeply nested input (regression test for + // stack exhaustion from fuzz input like [[[[...]]]]) + { + Json::CharReaderBuilder defaultBuilder; + Json::String nested(300, '['); + CharReaderPtr defaultReader(defaultBuilder.newCharReader()); + Json::String errs; + JSONTEST_ASSERT(!defaultReader->parse( + nested.data(), nested.data() + nested.size(), &root, &errs)); + JSONTEST_ASSERT( + errs == + "* Line 1, Column 257\n" + " Exceeded stackLimit for nested object and/or array values.\n"); + } +} + +// A nesting depth beyond the configured stackLimit must be reported as a +// regular parse failure through CharReader::parse, not as an escaping +// Json::RuntimeError that terminates the process when uncaught. +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseDeeplyNestedArrayFailsCleanly) { + Json::CharReaderBuilder b; + Json::Value root; + Json::String nested(1100, '['); + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = + reader->parse(nested.data(), nested.data() + nested.size(), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 257\n" + " Exceeded stackLimit for nested object and/or array " + "values.\n"); } JSONTEST_FIXTURE_LOCAL(CharReaderTest, testOperator) {