diff --git a/fuzz/object_convert_fuzzer.cpp b/fuzz/object_convert_fuzzer.cpp new file mode 100644 index 000000000..f282bcc6a --- /dev/null +++ b/fuzz/object_convert_fuzzer.cpp @@ -0,0 +1,186 @@ +// Fuzz harness: msgpack::object -> native type conversion surface. +// +// Threat model (why this harness should find bugs): +// - Applications that receive attacker-controlled msgpack immediately convert +// msgpack::object values into native C++ types (as() / convert()). +// The production harness (unpack_pack_fuzzer) never converts: the whole +// conversion surface is dark (report 20261108: v1/object.hpp 519 dark +// lines, 109/140 functions dark; int/ext/fixint/wstring/float/raw/string +// adaptors at 0%; vector/map/timespec adaptors are not even instantiated +// in the coverage build). +// - Expected bug classes: type confusion in convert specialisations (wrong +// union member read), integer overflow and float-cast overflow in numeric +// range checks (UBSan), OOB in container conversions that walk object +// arrays/maps, UTF-8 decoding bugs in the std::wstring adaptor, and +// ext/raw length handling on crafted lengths. +// - Mismatched conversions throw msgpack::type_error: that is the documented +// API contract, so each conversion is guarded individually and nothing is +// silenced. Numeric sinks move raw bits (never value-cast) so the harness +// itself cannot raise float-cast-overflow that the library is not +// responsible for. +// - The zone deep-copy (msgpack::object(o, zone)) exercises the +// object_with_zone clone path and instantiates the zone allocator, which +// no harness reaches today. +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +// Minimal C++11 replacement for FuzzedDataProvider (whose bundled header no +// longer compiles under the project's -std=c++11 build convention): option +// bytes are consumed from the tail of the input, the payload is what remains. +class byte_reader { +public: + byte_reader(const std::uint8_t* data, std::size_t size) + : d_(data), s_(size) {} + + std::uint8_t tail_u8() { + if (s_ == 0) return 0; + --s_; + return d_[s_]; + } + + const std::uint8_t* data() const { return d_; } + std::size_t remaining() const { return s_; } + +private: + const std::uint8_t* d_; + std::size_t s_; +}; + +void sink_u64(std::size_t v) { + static volatile std::size_t sink; + sink = v; +} + +void sink_double(double v) { + std::uint64_t bits = 0; + std::memcpy(&bits, &v, sizeof(bits)); + sink_u64(static_cast(bits)); +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < 4) return 0; + byte_reader br(data, size); + const std::uint8_t primary = br.tail_u8(); + const std::uint8_t secondary = br.tail_u8(); + const std::uint8_t container = br.tail_u8(); + const std::uint8_t option = br.tail_u8(); + std::vector payload(br.data(), br.data() + br.remaining()); + if (payload.empty()) return 0; + + try { + msgpack::object_handle oh = + msgpack::unpack(reinterpret_cast(payload.data()), + payload.size(), MSGPACK_NULLPTR, MSGPACK_NULLPTR, + msgpack::unpack_limit(64, 1024, 1024, 4096, 4096, + 4096)); + msgpack::object const o = oh.get(); + sink_u64(static_cast(o.type)); + sink_u64(o.is_nil() ? 1u : 0u); + + // Primary: as() matrix over the dark adaptors. Mismatches throw + // type_error (documented); each op is individually guarded so one + // mismatched conversion does not shadow the others. + try { + switch (primary % 14) { + case 0: { bool v = o.as(); sink_u64(v ? 1u : 0u); } break; + case 1: { std::uint64_t v = o.as(); sink_u64(static_cast(v)); } break; + case 2: { std::uint16_t v = o.as(); sink_u64(v); } break; + case 3: { std::uint8_t v = o.as(); sink_u64(v); } break; + case 4: { std::int64_t v = o.as(); sink_u64(static_cast(v)); } break; + case 5: { std::int8_t v = o.as(); sink_u64(static_cast(static_cast(v))); } break; + case 6: { float v = o.as(); sink_double(v); } break; + case 7: { double v = o.as(); sink_double(v); } break; + case 8: { std::string v = o.as(); sink_u64(v.size()); } break; + case 9: { std::wstring v = o.as(); sink_u64(v.size()); } break; + case 10: { msgpack::type::raw_ref v = o.as(); sink_u64(v.size); } break; + case 11: { msgpack::type::ext v = o.as(); sink_u64(v.size()); } break; + case 12: { msgpack::type::ext_ref v = o.as(); sink_u64(v.size()); } break; + case 13: { + // NOTE: this code base converts to global ::timespec + // (there is no msgpack::type::timespec here). + timespec v = o.as(); + sink_u64(static_cast(v.tv_sec)); + sink_u64(static_cast(v.tv_nsec)); + } break; + } + } catch (msgpack::type_error const&) {} + + // Secondary: reference-form convert(), convert_if_not_nil(), + // timespec reference conversion and v4raw_ref. + try { + switch (secondary % 6) { + case 0: { std::string v; o.convert(v); sink_u64(v.size()); } break; + case 1: { std::int32_t v = 0; o.convert(v); sink_u64(static_cast(v)); } break; + case 2: { std::string v; o.convert_if_not_nil(v); sink_u64(v.size()); } break; + case 3: { + // timespec via the reference-form convert() (global + // ::timespec; msgpack::type::timespec does not exist here). + timespec v; + std::memset(&v, 0, sizeof(v)); + o.convert(v); + sink_u64(static_cast(v.tv_sec)); + } break; + case 4: { msgpack::type::v4raw_ref v = o.as(); sink_u64(v.size); } break; + case 5: { + // reference-form int64 conversion (implicit_type is + // protected in this code base, so use convert() directly) + std::int64_t v = 0; + o.convert(v); + sink_u64(static_cast(v)); + } break; + } + } catch (msgpack::type_error const&) {} + + // Container conversions: instantiate adaptors absent from the + // coverage build today (vector, map, recursion into element + // adaptors). Deep-copy into a zone as well: object_with_zone clone + // path plus the zone allocator. + try { + switch (container % 4) { + case 0: { std::vector v = o.as>(); sink_u64(v.size()); } break; + case 1: { std::vector v = o.as>(); sink_u64(v.size()); } break; + case 2: { std::map v = o.as>(); sink_u64(v.size()); } break; + case 3: { std::vector v = o.as>(); sink_u64(v.size()); } break; + } + } catch (msgpack::type_error const&) {} + + try { + msgpack::zone z; + msgpack::object clone(o, z); + sink_u64(static_cast(clone.type)); + std::string s; + clone.convert(s); + sink_u64(s.size()); + } catch (msgpack::type_error const&) {} + + // Extra option bits: widen the numeric mix per input. + if (option & 1) { + try { std::uint32_t v = o.as(); sink_u64(v); } + catch (msgpack::type_error const&) {} + } + if (option & 2) { + try { double v = o.as(); sink_double(v); } + catch (msgpack::type_error const&) {} + } + if (option & 4) { + try { std::int16_t v = o.as(); sink_u64(static_cast(v)); } + catch (msgpack::type_error const&) {} + } + + } catch (msgpack::parse_error const&) { + // Invalid msgpack bytes: documented behaviour. + } catch (...) { + // bad_alloc and friends: documented behaviour. + } + return 0; +} diff --git a/fuzz/object_convert_fuzzer.dict b/fuzz/object_convert_fuzzer.dict new file mode 100644 index 000000000..06ab01b6d --- /dev/null +++ b/fuzz/object_convert_fuzzer.dict @@ -0,0 +1,45 @@ +"nil=\xc0" +"false=\xc2" +"true=\xc3" +"bin8=\xc4" +"bin16=\xc5" +"bin32=\xc6" +"ext8=\xc7" +"ext16=\xc8" +"ext32=\xc9" +"float32=\xca" +"float64=\xcb" +"uint8=\xcc" +"uint16=\xcd" +"uint32=\xce" +"uint64=\xcf" +"int8=\xd0" +"int16=\xd1" +"int32=\xd2" +"int64=\xd3" +"fixext1=\xd4" +"fixext2=\xd5" +"fixext4=\xd6" +"fixext8=\xd7" +"fixext16=\xd8" +"str8=\xd9" +"str16=\xda" +"str32=\xdb" +"array16=\xdc" +"array32=\xdd" +"map16=\xde" +"map32=\xdf" +"pos_fixint_min=\x00" +"pos_fixint_max=\x7f" +"neg_fixint_min=\xe0" +"neg_fixint_max=\xff" +"fixmap_min=\x80" +"fixmap_max=\x8f" +"fixarray_min=\x90" +"fixarray_max=\x9f" +"fixstr_min=\xa0" +"fixstr_max=\xbf" +"timestamp32=\xd6\xff" +"timestamp64=\xd7\xff" +"timestamp96=\xc7\x0c\xff" +"never_used=\xc1" diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Array32Short b/fuzz/object_convert_fuzzer_seed_corpus/Array32Short new file mode 100644 index 000000000..eec653fb2 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Array32Short differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Arrays b/fuzz/object_convert_fuzzer_seed_corpus/Arrays new file mode 100644 index 000000000..f426c24a6 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Arrays @@ -0,0 +1 @@ +’“’ \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Bin300 b/fuzz/object_convert_fuzzer_seed_corpus/Bin300 new file mode 100644 index 000000000..31913affd --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Bin300 @@ -0,0 +1 @@ +Å, \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/BinSizes b/fuzz/object_convert_fuzzer_seed_corpus/BinSizes new file mode 100644 index 000000000..c7a75abeb Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/BinSizes differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/ExtTypes b/fuzz/object_convert_fuzzer_seed_corpus/ExtTypes new file mode 100644 index 000000000..0b6b9677f --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/ExtTypes @@ -0,0 +1 @@ +ŌÕÖŲ \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/FixintEdges b/fuzz/object_convert_fuzzer_seed_corpus/FixintEdges new file mode 100644 index 000000000..fdc28a161 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/FixintEdges differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Floats b/fuzz/object_convert_fuzzer_seed_corpus/Floats new file mode 100644 index 000000000..e7008f04d Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Floats differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Int16Edges b/fuzz/object_convert_fuzzer_seed_corpus/Int16Edges new file mode 100644 index 000000000..60821b6de Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Int16Edges differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Int32Edges b/fuzz/object_convert_fuzzer_seed_corpus/Int32Edges new file mode 100644 index 000000000..5eb93a50c Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Int32Edges differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Int64Edges b/fuzz/object_convert_fuzzer_seed_corpus/Int64Edges new file mode 100644 index 000000000..b594731ca Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Int64Edges differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Int8Edges b/fuzz/object_convert_fuzzer_seed_corpus/Int8Edges new file mode 100644 index 000000000..4e86e1f06 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Int8Edges differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/MalformedMap b/fuzz/object_convert_fuzzer_seed_corpus/MalformedMap new file mode 100644 index 000000000..df64c08cf Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/MalformedMap differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/MapStrInt b/fuzz/object_convert_fuzzer_seed_corpus/MapStrInt new file mode 100644 index 000000000..c717962f3 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/MapStrInt @@ -0,0 +1 @@ +ƒ”x”y”z \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Maps b/fuzz/object_convert_fuzzer_seed_corpus/Maps new file mode 100644 index 000000000..f5557c69f --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Maps @@ -0,0 +1 @@ +€”a‚”k”v”j¢vv \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/MixedTree b/fuzz/object_convert_fuzzer_seed_corpus/MixedTree new file mode 100644 index 000000000..279793dd4 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/MixedTree differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/MultiMessage b/fuzz/object_convert_fuzzer_seed_corpus/MultiMessage new file mode 100644 index 000000000..307fac2bf --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/MultiMessage @@ -0,0 +1 @@ +Ā‘”z \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Nested32 b/fuzz/object_convert_fuzzer_seed_corpus/Nested32 new file mode 100644 index 000000000..d8a72681d --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Nested32 @@ -0,0 +1 @@ +‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘ \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/NilBool b/fuzz/object_convert_fuzzer_seed_corpus/NilBool new file mode 100644 index 000000000..ce2fe1dcb --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/NilBool @@ -0,0 +1 @@ +ĄĀĆ \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Str255 b/fuzz/object_convert_fuzzer_seed_corpus/Str255 new file mode 100644 index 000000000..3962a854b --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Str255 @@ -0,0 +1 @@ +Ł’aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Str256 b/fuzz/object_convert_fuzzer_seed_corpus/Str256 new file mode 100644 index 000000000..00d3fe1f8 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Str256 differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Str300 b/fuzz/object_convert_fuzzer_seed_corpus/Str300 new file mode 100644 index 000000000..b13495532 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/Str300 @@ -0,0 +1 @@ +Ś,cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/StrFix b/fuzz/object_convert_fuzzer_seed_corpus/StrFix new file mode 100644 index 000000000..fdfcc9dcb --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/StrFix @@ -0,0 +1 @@ +”x£abc \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/StrUtf8 b/fuzz/object_convert_fuzzer_seed_corpus/StrUtf8 new file mode 100644 index 000000000..2c9071b26 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/StrUtf8 @@ -0,0 +1 @@ +¦cafĆ©¤šŸ˜€ \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/Timestamps b/fuzz/object_convert_fuzzer_seed_corpus/Timestamps new file mode 100644 index 000000000..64ae12b37 Binary files /dev/null and b/fuzz/object_convert_fuzzer_seed_corpus/Timestamps differ diff --git a/fuzz/object_convert_fuzzer_seed_corpus/TruncatedStr b/fuzz/object_convert_fuzzer_seed_corpus/TruncatedStr new file mode 100644 index 000000000..e155fc020 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/TruncatedStr @@ -0,0 +1 @@ +Łab \ No newline at end of file diff --git a/fuzz/object_convert_fuzzer_seed_corpus/UintEdges b/fuzz/object_convert_fuzzer_seed_corpus/UintEdges new file mode 100644 index 000000000..b05d03467 --- /dev/null +++ b/fuzz/object_convert_fuzzer_seed_corpus/UintEdges @@ -0,0 +1 @@ +Ģ’Ķ’’Ī’’’’Ļ’’’’’’’’ \ No newline at end of file