From b026591e9e566b3ecd80a0161bd845756986c4b3 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 14 Mar 2017 21:05:38 +0100 Subject: [PATCH] :ambulance: added special case to fuzzers to fix #504 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since #329, NaN and inf numbers do not yield an exception, but are stored internally and are dumped as “null”. This commit adjusts the fuzz testers to deal with this special case. --- src/json.hpp | 39 ++++++++++++++++++++++++++++++++-- src/json.hpp.re2c | 39 ++++++++++++++++++++++++++++++++-- test/src/fuzzer-parse_cbor.cpp | 4 ++-- test/src/unit-regression.cpp | 20 +++++++++++++++++ 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 5511dcd51..baa421fa7 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -174,6 +174,7 @@ json.exception.parse_error.109 | parse error: array index 'one' is not a number json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. json.exception.parse_error.111 | parse error: bad input stream | Parsing CBOR or MessagePack from an input stream where the [`badbit` or `failbit`](http://en.cppreference.com/w/cpp/io/ios_base/iostate) is set. json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | | While parsing a map key, a value that is not a string has been read. @since version 3.0.0 */ @@ -8020,7 +8021,7 @@ class basic_json @param[in] v MessagePack serialization @param[in] idx byte index in @a v to check for a string - @throw std::invalid_argument if `v[idx]` does not belong to a string + @throw parse_error.113 if `v[idx]` does not belong to a string */ static void msgpack_expect_string(const std::vector& v, size_t idx) { @@ -8032,7 +8033,39 @@ class basic_json return; } - JSON_THROW(std::invalid_argument("error parsing a msgpack string @ " + std::to_string(idx) + ": " + std::to_string(static_cast(v[idx])))); + std::stringstream ss; + ss << std::hex << static_cast(v[idx]); + JSON_THROW(parse_error(113, idx + 1, "expected a MessagePack string; last byte: 0x" + ss.str())); + } + + /*! + @brief check if the next byte belongs to a string + + While parsing a map, the keys must be strings. This function checks if the + current byte is one of the start bytes for a string in CBOR: + + - 0x60 - 0x77: fixed length + - 0x78 - 0x7b: variable length + - 0x7f: indefinity length + + @param[in] v CBOR serialization + @param[in] idx byte index in @a v to check for a string + + @throw parse_error.113 if `v[idx]` does not belong to a string + */ + static void cbor_expect_string(const std::vector& v, size_t idx) + { + check_length(v.size(), 1, idx); + + const auto byte = v[idx]; + if ((byte >= 0x60 and byte <= 0x7b) or byte == 0x7f) + { + return; + } + + std::stringstream ss; + ss << std::hex << static_cast(v[idx]); + JSON_THROW(parse_error(113, idx + 1, "expected a CBOR string; last byte: 0x" + ss.str())); } /*! @@ -8046,6 +8079,7 @@ class basic_json @throw parse_error.110 if the given vector ends prematurely @throw parse_error.112 if unsupported features from MessagePack were used in the given vector @a v or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found @sa https://github.com/msgpack/msgpack/blob/master/spec.md */ @@ -8291,6 +8325,7 @@ class basic_json @throw parse_error.110 if the given vector ends prematurely @throw parse_error.112 if unsupported features from CBOR were used in the given vector @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found @sa https://tools.ietf.org/html/rfc7049 */ diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index f11736883..c77770574 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -174,6 +174,7 @@ json.exception.parse_error.109 | parse error: array index 'one' is not a number json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. json.exception.parse_error.111 | parse error: bad input stream | Parsing CBOR or MessagePack from an input stream where the [`badbit` or `failbit`](http://en.cppreference.com/w/cpp/io/ios_base/iostate) is set. json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | | While parsing a map key, a value that is not a string has been read. @since version 3.0.0 */ @@ -8020,7 +8021,7 @@ class basic_json @param[in] v MessagePack serialization @param[in] idx byte index in @a v to check for a string - @throw std::invalid_argument if `v[idx]` does not belong to a string + @throw parse_error.113 if `v[idx]` does not belong to a string */ static void msgpack_expect_string(const std::vector& v, size_t idx) { @@ -8032,7 +8033,39 @@ class basic_json return; } - JSON_THROW(std::invalid_argument("error parsing a msgpack string @ " + std::to_string(idx) + ": " + std::to_string(static_cast(v[idx])))); + std::stringstream ss; + ss << std::hex << static_cast(v[idx]); + JSON_THROW(parse_error(113, idx + 1, "expected a MessagePack string; last byte: 0x" + ss.str())); + } + + /*! + @brief check if the next byte belongs to a string + + While parsing a map, the keys must be strings. This function checks if the + current byte is one of the start bytes for a string in CBOR: + + - 0x60 - 0x77: fixed length + - 0x78 - 0x7b: variable length + - 0x7f: indefinity length + + @param[in] v CBOR serialization + @param[in] idx byte index in @a v to check for a string + + @throw parse_error.113 if `v[idx]` does not belong to a string + */ + static void cbor_expect_string(const std::vector& v, size_t idx) + { + check_length(v.size(), 1, idx); + + const auto byte = v[idx]; + if ((byte >= 0x60 and byte <= 0x7b) or byte == 0x7f) + { + return; + } + + std::stringstream ss; + ss << std::hex << static_cast(v[idx]); + JSON_THROW(parse_error(113, idx + 1, "expected a CBOR string; last byte: 0x" + ss.str())); } /*! @@ -8046,6 +8079,7 @@ class basic_json @throw parse_error.110 if the given vector ends prematurely @throw parse_error.112 if unsupported features from MessagePack were used in the given vector @a v or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found @sa https://github.com/msgpack/msgpack/blob/master/spec.md */ @@ -8291,6 +8325,7 @@ class basic_json @throw parse_error.110 if the given vector ends prematurely @throw parse_error.112 if unsupported features from CBOR were used in the given vector @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found @sa https://tools.ietf.org/html/rfc7049 */ diff --git a/test/src/fuzzer-parse_cbor.cpp b/test/src/fuzzer-parse_cbor.cpp index 6e7ae6362..4bcfc7eff 100644 --- a/test/src/fuzzer-parse_cbor.cpp +++ b/test/src/fuzzer-parse_cbor.cpp @@ -41,8 +41,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) // parse serialization json j2 = json::from_cbor(vec2); - // deserializations must match - assert(j1 == j2); + // serializations must match + assert(json::to_cbor(j2) == vec2); } catch (const json::parse_error&) { diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index 558a09558..bc45cb653 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -909,4 +909,24 @@ TEST_CASE("regression tests") CHECK(j["bool_vector"].dump() == "[false,true,false,false]"); } + + SECTION("issue #504 - assertion error (OSS-Fuzz 856)") + { + std::vector vec1 = {0xf9, 0xff, 0xff, 0x4a, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x37, 0x02, 0x38}; + json j1 = json::from_cbor(vec1); + + // step 2: round trip + std::vector vec2 = json::to_cbor(j1); + + // parse serialization + json j2 = json::from_cbor(vec2); + + // NaN is dumped to "null" + CHECK(j2.is_number_float()); + CHECK(std::isnan(j2.get())); + CHECK(j2.dump() == "null"); + + // check if serializations match + CHECK(json::to_cbor(j2) == vec2); + } }