From c33ff4fd9702b125a77b1ed5349bd0df84f88cb9 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Fri, 25 Sep 2026 16:24:28 +0200 Subject: [PATCH] verifier: validate NULL buffer in Verifier and flexbuffers::VerifyBuffer 1. In flexbuffers.h, Verifier::VerifyBuffer() performed pointer arithmetic (buf_ + size_) and immediately dereferenced *--end without validating that buf_ is non-null. When a null buffer is passed with size_ >= 3, this leads to a segmentation fault rather than returning false. Additionally, flexbuffers::VerifyBuffer() instantiated the verifier and attempted to clear and resize reuse_tracker_ even when buf is NULL. Add null checks in Verifier::VerifyBuffer(), flexbuffers::VerifyBuffer(), Verifier::VerifyFrom, Verifier::VerifyBefore, and guard reuse_tracker_ initialization. 2. In verifier.h, VerifierTemplate::Verify() and VerifyBufferFromStart() checked buffer bounds against size_ but did not verify that buf_ is non-null. When a Verifier is initialized with a null buffer pointer and VerifyBuffer() is invoked, Verify() returned true for initial offsets, causing ReadScalar() to dereference a null pointer. Add buf_ != nullptr checks to VerifierTemplate::Verify() and VerifyBufferFromStart(). 3. In tests/flexbuffers_test.cpp and tests/test.cpp, add regression tests verifying that passing null buffer pointers to flexbuffers::VerifyBuffer() and flatbuffers::Verifier gracefully returns false without crashing. Signed-off-by: Filippo Tedeschi --- include/flatbuffers/flexbuffers.h | 10 ++++++---- include/flatbuffers/verifier.h | 7 +++++-- tests/flexbuffers_test.cpp | 9 +++++++++ tests/flexbuffers_test.h | 1 + tests/test.cpp | 8 ++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/include/flatbuffers/flexbuffers.h b/include/flatbuffers/flexbuffers.h index 5c42a7ed47..9899f7273b 100644 --- a/include/flatbuffers/flexbuffers.h +++ b/include/flatbuffers/flexbuffers.h @@ -1860,7 +1860,7 @@ class Verifier FLATBUFFERS_FINAL_CLASS { reuse_tracker_(reuse_tracker) { FLATBUFFERS_ASSERT(static_cast(size_) < FLATBUFFERS_MAX_BUFFER_SIZE); - if (reuse_tracker_) { + if (buf_ && reuse_tracker_) { reuse_tracker_->clear(); reuse_tracker_->resize(size_, PackedType(BIT_WIDTH_8, FBT_NULL)); } @@ -1879,10 +1879,11 @@ class Verifier FLATBUFFERS_FINAL_CLASS { // Verify any range within the buffer. bool VerifyFrom(size_t elem, size_t elem_len) const { - return Check(elem_len < size_ && elem <= size_ - elem_len); + return Check(buf_ != nullptr && elem_len < size_ && + elem <= size_ - elem_len); } bool VerifyBefore(size_t elem, size_t elem_len) const { - return Check(elem_len <= elem); + return Check(buf_ != nullptr && elem_len <= elem); } bool VerifyFromPointer(const uint8_t* p, size_t len) { @@ -2050,7 +2051,7 @@ class Verifier FLATBUFFERS_FINAL_CLASS { public: bool VerifyBuffer() { - if (!Check(size_ >= 3)) return false; + if (!Check(buf_ != nullptr && size_ >= 3)) return false; auto end = buf_ + size_; auto byte_width = *--end; auto packed_type = *--end; @@ -2073,6 +2074,7 @@ class Verifier FLATBUFFERS_FINAL_CLASS { // parameters. inline bool VerifyBuffer(const uint8_t* buf, size_t buf_len, std::vector* reuse_tracker = nullptr) { + if (!buf) return false; Verifier verifier(buf, buf_len, reuse_tracker); return verifier.VerifyBuffer(); } diff --git a/include/flatbuffers/verifier.h b/include/flatbuffers/verifier.h index a0b793597c..9bbe168b16 100644 --- a/include/flatbuffers/verifier.h +++ b/include/flatbuffers/verifier.h @@ -83,7 +83,8 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { upper_bound_ = upper_bound; } } - return Check(elem_len < size_ && elem <= size_ - elem_len); + return Check(buf_ != nullptr && elem_len < size_ && + elem <= size_ - elem_len); } bool VerifyAlignment(const size_t elem, const size_t align) const { @@ -204,7 +205,9 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { // Buffers have to be of some size to be valid. The reason it is a runtime // check instead of static_assert, is that nested flatbuffers go through // this call and their size is determined at runtime. - if (!Check(size_ >= FLATBUFFERS_MIN_BUFFER_SIZE)) return false; + if (!Check(buf_ != nullptr && size_ >= FLATBUFFERS_MIN_BUFFER_SIZE)) { + return false; + } // If an identifier is provided, check that we have a buffer if (identifier && !Check((size_ >= 2 * sizeof(flatbuffers::uoffset_t) && diff --git a/tests/flexbuffers_test.cpp b/tests/flexbuffers_test.cpp index 6087a0affb..97ac56f0c6 100644 --- a/tests/flexbuffers_test.cpp +++ b/tests/flexbuffers_test.cpp @@ -314,5 +314,14 @@ void ParseFlexbuffersFromJsonWithNullTest() { } } +void FlexBuffersVerifyNullBufferTest() { + TEST_EQ(flexbuffers::VerifyBuffer(nullptr, 0), false); + TEST_EQ(flexbuffers::VerifyBuffer(nullptr, 10), false); + std::vector reuse_tracker; + TEST_EQ(flexbuffers::VerifyBuffer(nullptr, 10, &reuse_tracker), false); + flexbuffers::Verifier verifier(nullptr, 10); + TEST_EQ(verifier.VerifyBuffer(), false); +} + } // namespace tests } // namespace flatbuffers diff --git a/tests/flexbuffers_test.h b/tests/flexbuffers_test.h index 132098fb37..40b1a1543a 100644 --- a/tests/flexbuffers_test.h +++ b/tests/flexbuffers_test.h @@ -9,6 +9,7 @@ void FlexBuffersReuseBugTest(); void FlexBuffersFloatingPointTest(); void FlexBuffersDeprecatedTest(); void ParseFlexbuffersFromJsonWithNullTest(); +void FlexBuffersVerifyNullBufferTest(); } // namespace tests } // namespace flatbuffers diff --git a/tests/test.cpp b/tests/test.cpp index 5a43546f53..789e3a5d8a 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1319,6 +1319,12 @@ void SizeVerifierTest() { TEST_EQ(length, size_verifier.GetComputedSize()); } +void VerifierNullBufferTest() { + flatbuffers::Verifier null_verifier(nullptr, 100); + TEST_EQ(false, null_verifier.VerifyBuffer()); + TEST_EQ(false, null_verifier.Verify(0, 10)); +} + template void TestIterators(const std::vector& expected, const Container& tested) { TEST_ASSERT(tested.rbegin().base() == tested.end()); @@ -1840,6 +1846,7 @@ int FlatBufferTests(const std::string& tests_data_path) { NativeTypeTest(); OptionalScalarsTest(); ParseFlexbuffersFromJsonWithNullTest(); + FlexBuffersVerifyNullBufferTest(); FlatbuffersSpanTest(); FixedLengthArrayConstructorTest(); FixedLengthArrayOperatorEqualTest(); @@ -1850,6 +1857,7 @@ int FlatBufferTests(const std::string& tests_data_path) { WarningsAsErrorsTest(); NestedVerifierTest(); SizeVerifierTest(); + VerifierNullBufferTest(); PrivateAnnotationsLeaks(); JsonUnsortedArrayTest(); VectorSpanTest();