From 1f53c6c041eee6321eb18b5dc7f685f80cd62599 Mon Sep 17 00:00:00 2001 From: thebusytypist Date: Tue, 15 Jul 2014 14:16:06 +0800 Subject: [PATCH] Implement stack size limitation for iterative parsing. --- include/rapidjson/document.h | 45 ++++++++++++++++++++------------- include/rapidjson/error/en.h | 1 + include/rapidjson/error/error.h | 3 ++- include/rapidjson/reader.h | 34 ++++++++++++++++++++++--- test/unittest/readertest.cpp | 12 +++++++++ 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 4448600..d94cd62 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1221,12 +1221,13 @@ public: \tparam SourceEncoding Encoding of input stream \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ template - GenericDocument& ParseStream(InputStream& is) { + GenericDocument& ParseStream(InputStream& is, size_t limit = 0) { ValueType::SetNull(); // Remove existing root if exist - GenericReader reader(&GetAllocator()); + GenericReader reader(limit, &GetAllocator()); ClearStackOnExit scope(*this); parseResult_ = reader.template Parse(is, *this); if (parseResult_) { @@ -1240,21 +1241,23 @@ public: /*! \tparam parseFlags Combination of \ref ParseFlag. \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ template - GenericDocument& ParseStream(InputStream& is) { - return ParseStream(is); + GenericDocument& ParseStream(InputStream& is, size_t limit = 0) { + return ParseStream(is, limit); } //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) /*! \tparam InputStream Type of input stream, implementing Stream concept \param is Input stream to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ template - GenericDocument& ParseStream(InputStream& is) { - return ParseStream(is); + GenericDocument& ParseStream(InputStream& is, size_t limit = 0) { + return ParseStream(is, limit); } //!@} @@ -1265,30 +1268,33 @@ public: /*! \tparam parseFlags Combination of \ref ParseFlag. \tparam SourceEncoding Transcoding from input Encoding \param str Mutable zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ template - GenericDocument& ParseInsitu(Ch* str) { + GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) { GenericInsituStringStream s(str); - return ParseStream(s); + return ParseStream(s, limit); } //! Parse JSON text from a mutable string /*! \tparam parseFlags Combination of \ref ParseFlag. \param str Mutable zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ template - GenericDocument& ParseInsitu(Ch* str) { - return ParseInsitu(str); + GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) { + return ParseInsitu(str, limit); } //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) /*! \param str Mutable zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. \return The document itself for fluent API. */ - GenericDocument& ParseInsitu(Ch* str) { - return ParseInsitu(str); + GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) { + return ParseInsitu(str, limit); } //!@} @@ -1299,28 +1305,31 @@ public: /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). \tparam SourceEncoding Transcoding from input Encoding \param str Read-only zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. */ template - GenericDocument& Parse(const Ch* str) { + GenericDocument& Parse(const Ch* str, size_t limit = 0) { RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); GenericStringStream s(str); - return ParseStream(s); + return ParseStream(s, limit); } //! Parse JSON text from a read-only string /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). \param str Read-only zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. */ template - GenericDocument& Parse(const Ch* str) { - return Parse(str); + GenericDocument& Parse(const Ch* str, size_t limit = 0) { + return Parse(str, limit); } //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) /*! \param str Read-only zero-terminated string to be parsed. + \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. */ - GenericDocument& Parse(const Ch* str) { - return Parse(str); + GenericDocument& Parse(const Ch* str, size_t limit = 0) { + return Parse(str, limit); } //!@} diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h index e9120c5..e9a5d1d 100644 --- a/include/rapidjson/error/en.h +++ b/include/rapidjson/error/en.h @@ -40,6 +40,7 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErro case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + case kParseErrorStackSizeLimitExceeded: return RAPIDJSON_ERROR_STRING("Parsing stack size limit is exceeded."); default: return RAPIDJSON_ERROR_STRING("Unknown error."); diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index e5c2b1b..a47dfaa 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -59,7 +59,8 @@ enum ParseErrorCode { kParseErrorNumberMissExponent, //!< Miss exponent in number. kParseErrorTermination, //!< Parsing was terminated. - kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. + kParseErrorUnspecificSyntaxError, //!< Unspecific syntax error. + kParseErrorStackSizeLimitExceeded //!< Parsing stack size limit is exceeded. }; //! Result of parsing (wraps ParseErrorCode) diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index bdfd826..95a2996 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -272,10 +272,11 @@ public: typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type //! Constructor. - /*! \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + /*! \param limit Parsing stack size limit(in bytes). Pass 0 means no limit. + \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) */ - GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseResult_() {} + GenericReader(size_t limit = 0, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), kStackSizeLimit(limit), parseResult_() {} //! Parse JSON text. /*! \tparam parseFlags Combination of \ref ParseFlag. @@ -569,8 +570,14 @@ private: if (c == '\\') { // Escape is.Take(); Ch e = is.Take(); - if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) { + if (!(parseFlags & kParseInsituFlag)) { + if (!IsStackSpaceSufficient(1)) { + RAPIDJSON_PARSE_ERROR(kParseErrorStackSizeLimitExceeded, is.Tell() - 1); + } + } os.Put(escape[(unsigned char)e]); + } else if (e == 'u') { // Unicode unsigned codepoint = ParseHex4(is); if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { @@ -589,6 +596,11 @@ private: } else if (c == '"') { // Closing double quote is.Take(); + if (!(parseFlags & kParseInsituFlag)) { + if (!IsStackSpaceSufficient(1)) { + RAPIDJSON_PARSE_ERROR(kParseErrorStackSizeLimitExceeded, is.Tell() - 1); + } + } os.Put('\0'); // null-terminate the string return; } @@ -1038,8 +1050,16 @@ private: else if (src == IterativeParsingKeyValueDelimiterState) n = IterativeParsingMemberValueState; // Push current state. + if (!IsStackSpaceSufficient(1)) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStackSizeLimitExceeded, is.Tell()); + return IterativeParsingErrorState; + } *stack_.template Push(1) = n; // Initialize and push the member/element count. + if (!IsStackSpaceSufficient(1)) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStackSizeLimitExceeded, is.Tell()); + return IterativeParsingErrorState; + } *stack_.template Push(1) = 0; // Call handler if (dst == IterativeParsingObjectInitialState) @@ -1206,7 +1226,13 @@ private: return parseResult_; } - static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + template + bool IsStackSpaceSufficient(size_t count) const { + return kStackSizeLimit == 0 || (stack_.GetSize() + sizeof(T) * count <= kStackSizeLimit); + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + const size_t kStackSizeLimit; //!< Stack size limit(in bytes). A value of 0 means no limit. internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. ParseResult parseResult_; }; // class GenericReader diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index 0bd1b13..9c668cd 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -1288,6 +1288,18 @@ TEST(Reader, IterativeParsing_ShortCircuit) { } } +TEST(Reader, IterativeParsing_LimitStackSize) { + BaseReaderHandler<> handler; + Reader reader(20); + StringStream is("[[[]]]"); + + ParseResult r = reader.Parse(is, handler); + + EXPECT_TRUE(reader.HasParseError()); + EXPECT_EQ(kParseErrorStackSizeLimitExceeded, r.Code()); + EXPECT_EQ(2, r.Offset()); +} + #ifdef __GNUC__ RAPIDJSON_DIAG_POP #endif