diff --git a/include/flatbuffers/minireflect.h b/include/flatbuffers/minireflect.h index 77be5165111..2d2f6896fc3 100644 --- a/include/flatbuffers/minireflect.h +++ b/include/flatbuffers/minireflect.h @@ -17,6 +17,8 @@ #ifndef FLATBUFFERS_MINIREFLECT_H_ #define FLATBUFFERS_MINIREFLECT_H_ +#include + #include "flatbuffers/flatbuffers.h" #include "flatbuffers/util.h" @@ -125,89 +127,240 @@ const char* EnumName(T tval, const TypeTable* type_table) { return nullptr; } +inline bool MiniReflectInBounds(const uint8_t* p, size_t size, + const uint8_t* begin, const uint8_t* end) { + if (!begin || !end) return true; + if (!p || p < begin || p > end) return false; + return size <= static_cast(end - p); +} + +template +inline bool MiniReflectReadScalar(const uint8_t* p, const uint8_t* begin, + const uint8_t* end, T* val) { + if (!MiniReflectInBounds(p, sizeof(T), begin, end)) return false; + *val = ReadScalar(p); + return true; +} + +inline bool MiniReflectAdvance(const uint8_t* p, size_t offset, + const uint8_t* begin, const uint8_t* end, + const uint8_t** out) { + if (!p || !out) return false; + if (!begin || !end) { + *out = p + offset; + return true; + } + if (p > end || offset > static_cast(end - p)) return false; + *out = p + offset; + return true; +} + +inline const uint8_t* MiniReflectGetTableFieldAddress(const uint8_t* obj, + voffset_t field, + const uint8_t* begin, + const uint8_t* end) { + if (!begin || !end) { + return reinterpret_cast(obj)->GetAddressOf(field); + } + soffset_t voff = 0; + if (!MiniReflectReadScalar(obj, begin, end, &voff)) return nullptr; + if (voff < 0 || static_cast(voff) > static_cast(obj - begin)) { + return nullptr; + } + const auto* vtable = obj - voff; + voffset_t vsize = 0; + voffset_t tsize = 0; + if (!MiniReflectReadScalar(vtable, begin, end, &vsize) || + !MiniReflectReadScalar(vtable + sizeof(voffset_t), begin, end, &tsize) || + field >= vsize) { + return nullptr; + } + voffset_t foff = 0; + if (!MiniReflectReadScalar(vtable + field, begin, end, &foff) || + !foff || foff >= tsize) { + return nullptr; + } + const uint8_t* p = nullptr; + return MiniReflectAdvance(obj, foff, begin, end, &p) ? p : nullptr; +} + void IterateObject(const uint8_t* obj, const TypeTable* type_table, - IterationVisitor* visitor); + IterationVisitor* visitor, const uint8_t* begin = nullptr, + const uint8_t* end = nullptr); inline void IterateValue(ElementaryType type, const uint8_t* val, const TypeTable* type_table, const uint8_t* prev_val, - soffset_t vector_index, IterationVisitor* visitor) { + soffset_t vector_index, IterationVisitor* visitor, + const uint8_t* begin = nullptr, + const uint8_t* end = nullptr) { switch (type) { case ET_UTYPE: { - auto tval = ReadScalar(val); + uint8_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->UType(tval, EnumName(tval, type_table)); break; } case ET_BOOL: { - visitor->Bool(ReadScalar(val) != 0); + uint8_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } + visitor->Bool(tval != 0); break; } case ET_CHAR: { - auto tval = ReadScalar(val); + int8_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->Char(tval, EnumName(tval, type_table)); break; } case ET_UCHAR: { - auto tval = ReadScalar(val); + uint8_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->UChar(tval, EnumName(tval, type_table)); break; } case ET_SHORT: { - auto tval = ReadScalar(val); + int16_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->Short(tval, EnumName(tval, type_table)); break; } case ET_USHORT: { - auto tval = ReadScalar(val); + uint16_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->UShort(tval, EnumName(tval, type_table)); break; } case ET_INT: { - auto tval = ReadScalar(val); + int32_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->Int(tval, EnumName(tval, type_table)); break; } case ET_UINT: { - auto tval = ReadScalar(val); + uint32_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } visitor->UInt(tval, EnumName(tval, type_table)); break; } case ET_LONG: { - visitor->Long(ReadScalar(val)); + int64_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } + visitor->Long(tval); break; } case ET_ULONG: { - visitor->ULong(ReadScalar(val)); + uint64_t tval = 0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } + visitor->ULong(tval); break; } case ET_FLOAT: { - visitor->Float(ReadScalar(val)); + float tval = 0.0f; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } + visitor->Float(tval); break; } case ET_DOUBLE: { - visitor->Double(ReadScalar(val)); + double tval = 0.0; + if (!MiniReflectReadScalar(val, begin, end, &tval)) { + visitor->Unknown(val); + return; + } + visitor->Double(tval); break; } case ET_STRING: { - val += ReadScalar(val); - visitor->String(reinterpret_cast(val)); + uoffset_t off = 0; + const uint8_t* str = nullptr; + uoffset_t len = 0; + if (!MiniReflectReadScalar(val, begin, end, &off) || + !MiniReflectAdvance(val, off, begin, end, &str) || + !MiniReflectReadScalar(str, begin, end, &len) || + !MiniReflectInBounds(str, sizeof(uoffset_t) + len + 1, begin, end)) { + visitor->Unknown(val); + return; + } + visitor->String(reinterpret_cast(str)); break; } case ET_SEQUENCE: { switch (type_table->st) { - case ST_TABLE: - val += ReadScalar(val); - IterateObject(val, type_table, visitor); + case ST_TABLE: { + uoffset_t off = 0; + const uint8_t* ptr = nullptr; + if (!MiniReflectReadScalar(val, begin, end, &off) || + !MiniReflectAdvance(val, off, begin, end, &ptr)) { + visitor->Unknown(val); + return; + } + IterateObject(ptr, type_table, visitor, begin, end); break; + } case ST_STRUCT: - IterateObject(val, type_table, visitor); + IterateObject(val, type_table, visitor, begin, end); break; case ST_UNION: { - val += ReadScalar(val); + uoffset_t off = 0; + const uint8_t* ptr = nullptr; + if (!MiniReflectReadScalar(val, begin, end, &off) || + !MiniReflectAdvance(val, off, begin, end, &ptr)) { + visitor->Unknown(val); + return; + } FLATBUFFERS_ASSERT(prev_val); - auto union_type = *prev_val; // Always a uint8_t. + uint8_t union_type = 0; + if (!MiniReflectReadScalar(prev_val, begin, end, &union_type)) { + visitor->Unknown(val); + return; + } if (vector_index >= 0) { - auto type_vec = reinterpret_cast*>(prev_val); - union_type = type_vec->Get(static_cast(vector_index)); + uoffset_t type_vec_size = 0; + const uint8_t* type_vec_data = nullptr; + if (!MiniReflectReadScalar(prev_val, begin, end, &type_vec_size) || + !MiniReflectAdvance(prev_val, sizeof(uoffset_t), begin, end, + &type_vec_data) || + !MiniReflectInBounds(type_vec_data, type_vec_size, begin, end) || + static_cast(vector_index) >= type_vec_size || + !MiniReflectReadScalar( + type_vec_data + static_cast(vector_index), begin, + end, + &union_type)) { + visitor->Unknown(val); + return; + } } auto type_code_idx = LookupEnum(union_type, type_table->values, type_table->num_elems); @@ -217,17 +370,27 @@ inline void IterateValue(ElementaryType type, const uint8_t* val, switch (type_code.base_type) { case ET_SEQUENCE: { auto ref = type_table->type_refs[type_code.sequence_ref](); - IterateObject(val, ref, visitor); + IterateObject(ptr, ref, visitor, begin, end); break; } case ET_STRING: - visitor->String(reinterpret_cast(val)); + { + uoffset_t len = 0; + if (!MiniReflectReadScalar(ptr, begin, end, &len) || + !MiniReflectInBounds(ptr, + sizeof(uoffset_t) + len + 1, + begin, end)) { + visitor->Unknown(ptr); + return; + } + visitor->String(reinterpret_cast(ptr)); + } break; default: - visitor->Unknown(val); + visitor->Unknown(ptr); } } else { - visitor->Unknown(val); + visitor->Unknown(ptr); } break; } @@ -245,7 +408,8 @@ inline void IterateValue(ElementaryType type, const uint8_t* val, } inline void IterateObject(const uint8_t* obj, const TypeTable* type_table, - IterationVisitor* visitor) { + IterationVisitor* visitor, const uint8_t* begin, + const uint8_t* end) { visitor->StartSequence(); const uint8_t* prev_val = nullptr; size_t set_idx = 0; @@ -262,10 +426,13 @@ inline void IterateObject(const uint8_t* obj, const TypeTable* type_table, auto name = type_table->names ? type_table->names[i] : nullptr; const uint8_t* val = nullptr; if (type_table->st == ST_TABLE) { - val = reinterpret_cast(obj)->GetAddressOf( - FieldIndexToOffset(static_cast(i))); + val = MiniReflectGetTableFieldAddress( + obj, FieldIndexToOffset(static_cast(i)), begin, end); } else { - val = obj + type_table->values[i]; + if (!MiniReflectAdvance(obj, type_table->values[i], begin, end, &val)) { + visitor->Unknown(obj); + return; + } } visitor->Field(i, set_idx, type, is_repeating, ref, name, val); if (val) { @@ -275,25 +442,51 @@ inline void IterateObject(const uint8_t* obj, const TypeTable* type_table, size_t size = 0; if (type_table->st == ST_TABLE) { // variable length vector - val += ReadScalar(val); - auto vec = reinterpret_cast*>(val); - elem_ptr = vec->Data(); - size = vec->size(); + uoffset_t off = 0; + uoffset_t vec_size = 0; + if (!MiniReflectReadScalar(val, begin, end, &off) || + !MiniReflectAdvance(val, off, begin, end, &val) || + !MiniReflectReadScalar(val, begin, end, &vec_size)) { + visitor->Unknown(val); + return; + } + if (!MiniReflectAdvance(val, sizeof(uoffset_t), begin, end, + &elem_ptr)) { + visitor->Unknown(val); + return; + } + size = vec_size; + const auto elem_size = InlineSize(type, ref); + if (size > (std::numeric_limits::max)() / elem_size) { + visitor->Unknown(elem_ptr); + return; + } + const auto bytes = size * elem_size; + if (!MiniReflectInBounds(elem_ptr, bytes, begin, end)) { + visitor->Unknown(elem_ptr); + return; + } } else { // otherwise fixed size array size = type_table->array_sizes[array_idx]; ++array_idx; + const auto elem_size = InlineSize(type, ref); + if (size > (std::numeric_limits::max)() / elem_size || + !MiniReflectInBounds(elem_ptr, size * elem_size, begin, end)) { + visitor->Unknown(elem_ptr); + return; + } } visitor->StartVector(); for (size_t j = 0; j < size; j++) { visitor->Element(j, type, ref, elem_ptr); IterateValue(type, elem_ptr, ref, prev_val, static_cast(j), - visitor); + visitor, begin, end); elem_ptr += InlineSize(type, ref); } visitor->EndVector(); } else { - IterateValue(type, val, ref, prev_val, -1, visitor); + IterateValue(type, val, ref, prev_val, -1, visitor, begin, end); } } prev_val = val; @@ -304,7 +497,23 @@ inline void IterateObject(const uint8_t* obj, const TypeTable* type_table, inline void IterateFlatBuffer(const uint8_t* buffer, const TypeTable* type_table, IterationVisitor* callback) { - IterateObject(GetRoot(buffer), type_table, callback); + IterateObject(GetRoot(buffer), type_table, callback, nullptr, + nullptr); +} + +inline void IterateFlatBuffer(const uint8_t* buffer, size_t buffer_size, + const TypeTable* type_table, + IterationVisitor* callback) { + if (buffer_size < sizeof(uoffset_t)) return; + const auto* begin = buffer; + const auto* end = buffer + buffer_size; + uoffset_t root = 0; + const uint8_t* obj = nullptr; + if (!MiniReflectReadScalar(buffer, begin, end, &root) || + !MiniReflectAdvance(buffer, root, begin, end, &obj)) { + return; + } + IterateObject(obj, type_table, callback, begin, end); } // Outputting a Flatbuffer to a string. Tries to conform as close to JSON / @@ -424,6 +633,19 @@ struct ToStringVisitor : public IterationVisitor { } }; +inline std::string FlatBufferToString(const uint8_t* buffer, + size_t buffer_size, + const TypeTable* type_table, + bool multi_line = false, + bool vector_delimited = true, + const std::string& indent = "", + bool quotes = false) { + ToStringVisitor tostring_visitor(multi_line ? "\n" : " ", quotes, indent, + vector_delimited); + IterateFlatBuffer(buffer, buffer_size, type_table, &tostring_visitor); + return tostring_visitor.s; +} + inline std::string FlatBufferToString(const uint8_t* buffer, const TypeTable* type_table, bool multi_line = false, diff --git a/tests/reflection_test.cpp b/tests/reflection_test.cpp index 1c489185204..17d287289d8 100644 --- a/tests/reflection_test.cpp +++ b/tests/reflection_test.cpp @@ -6,6 +6,7 @@ #include "flatbuffers/verifier.h" #include "monster_test.h" #include "monster_test_generated.h" +#include "samples/monster_generated.h" #include "test_assert.h" #include "tests/arrays_test_generated.h" @@ -322,5 +323,23 @@ void MiniReflectFixedLengthArrayTest() { #endif } +void MiniReflectBoundsTest() { + std::vector buf = { + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0xff, 0x00, + 0x0c, 0x00, 0x07, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x03, 0x00, 0x00, 0x00, 0x4f, 0x72, 0x63, 0x00, + }; + + flatbuffers::Verifier verifier(buf.data(), buf.size()); + TEST_EQ(verifier.VerifyBuffer(), true); + + auto s = flatbuffers::FlatBufferToString( + buf.data(), buf.size(), MyGame::Sample::Monster::MiniReflectTypeTable()); + TEST_EQ(!s.empty(), true); +} + } // namespace tests } // namespace flatbuffers diff --git a/tests/reflection_test.h b/tests/reflection_test.h index fa6f470c697..6a8deb0800f 100644 --- a/tests/reflection_test.h +++ b/tests/reflection_test.h @@ -12,6 +12,7 @@ void ReflectionTest(const std::string& tests_data_path, uint8_t* flatbuf, size_t length); void MiniReflectFixedLengthArrayTest(); void MiniReflectFlatBuffersTest(uint8_t* flatbuf); +void MiniReflectBoundsTest(); } // namespace tests } // namespace flatbuffers diff --git a/tests/test.cpp b/tests/test.cpp index 68e8e68391c..e1434b1e33f 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1763,6 +1763,7 @@ int FlatBufferTests(const std::string& tests_data_path) { MiniReflectFlatBuffersTest(flatbuf.data()); MiniReflectFixedLengthArrayTest(); + MiniReflectBoundsTest(); SizePrefixedTest();