From 66b28017f87e55f5ebb4acce3c73e15cca635121 Mon Sep 17 00:00:00 2001 From: sunn11y <742917877@qq.com> Date: Thu, 12 Mar 2026 23:41:06 +0800 Subject: [PATCH 1/2] Add checked FlatBuffers C++ root helpers backed by verifier --- include/flatbuffers/buffer.h | 74 ++++++++++++++++++++++++++++++++++++ tests/monster_test.cpp | 21 ++++++++++ 2 files changed, 95 insertions(+) diff --git a/include/flatbuffers/buffer.h b/include/flatbuffers/buffer.h index 154d187ab75..58bc4ffc7fd 100644 --- a/include/flatbuffers/buffer.h +++ b/include/flatbuffers/buffer.h @@ -21,6 +21,7 @@ #include "flatbuffers/base.h" #include "flatbuffers/stl_emulation.h" +#include "flatbuffers/verifier.h" namespace flatbuffers { @@ -220,6 +221,79 @@ const T* GetSizePrefixedRoot(const void* buf) { return GetRoot(reinterpret_cast(buf) + sizeof(SizeT)); } +// Checked variants of the root access helpers that first run the FlatBuffers +// verifier on the provided buffer. These are intended as secure-by-default +// helpers for callers that are dealing with untrusted input and want a single +// API that combines verification with obtaining the typed root pointer. +// +// If verification fails, these functions return nullptr instead of a typed +// pointer into the buffer. +template +const T* GetRootChecked(const void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifyBuffer()) return nullptr; + return GetRoot(buf); +} + +template +const T* GetRootChecked(const void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifyBuffer(identifier)) return nullptr; + return GetRoot(buf); +} + +template +const T* GetSizePrefixedRootChecked( + const void* buf, size_t len, const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifySizePrefixedBuffer(nullptr)) return nullptr; + return GetSizePrefixedRoot(buf); +} + +template +const T* GetSizePrefixedRootChecked( + const void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifySizePrefixedBuffer(identifier)) return nullptr; + return GetSizePrefixedRoot(buf); +} + +template +T* GetMutableRootChecked(void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast( + GetRootChecked(const_cast(buf), len, opts)); +} + +template +T* GetMutableRootChecked(void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetRootChecked(const_cast(buf), len, + identifier, opts)); +} + +template +T* GetMutableSizePrefixedRootChecked( + void* buf, size_t len, const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetSizePrefixedRootChecked( + const_cast(buf), len, opts)); +} + +template +T* GetMutableSizePrefixedRootChecked( + void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetSizePrefixedRootChecked( + const_cast(buf), len, identifier, opts)); +} + } // namespace flatbuffers #endif // FLATBUFFERS_BUFFER_H_ diff --git a/tests/monster_test.cpp b/tests/monster_test.cpp index 9b188afa246..8417f97afdf 100644 --- a/tests/monster_test.cpp +++ b/tests/monster_test.cpp @@ -421,6 +421,27 @@ void AccessFlatBufferTest(const uint8_t* flatbuf, size_t length, bool pooled) { TEST_EQ(GetBufferStartFromRootPointer(monster), flatbuf); } +// Basic coverage for the checked root helper APIs that combine verification +// with obtaining the typed root pointer. +void CheckedRootApiTest(const uint8_t* flatbuf, size_t length) { + // Valid buffer should round-trip through the checked helpers. + auto monster_checked = + flatbuffers::GetRootChecked(flatbuf, length, MonsterIdentifier()); + TEST_NOTNULL(monster_checked); + TEST_EQ(monster_checked->hp(), 80); + TEST_EQ(monster_checked->mana(), 150); + + // Mutating the size downward should cause verification to fail and return + // nullptr from the checked helpers, while the unchecked helpers remain + // undefined for such input. + if (length > 4) { + const size_t truncated = length - 4; + auto truncated_checked = + flatbuffers::GetRootChecked(flatbuf, truncated, MonsterIdentifier()); + TEST_EQ(truncated_checked == nullptr, true); + } +} + // Change a FlatBuffer in-place, after it has been constructed. void MutateFlatBuffersTest(uint8_t* flatbuf, std::size_t length) { // Get non-const pointer to root. From b3211166204f91158324ea4e2abb55ac9d74683b Mon Sep 17 00:00:00 2001 From: 0xEaS1 <742917877@qq.com> Date: Fri, 13 Mar 2026 20:04:51 +0800 Subject: [PATCH 2/2] Add checked C++ root helpers backed by verifier --- include/flatbuffers/buffer.h | 74 ----------------------------- include/flatbuffers/flatbuffers.h | 79 +++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 74 deletions(-) diff --git a/include/flatbuffers/buffer.h b/include/flatbuffers/buffer.h index 58bc4ffc7fd..154d187ab75 100644 --- a/include/flatbuffers/buffer.h +++ b/include/flatbuffers/buffer.h @@ -21,7 +21,6 @@ #include "flatbuffers/base.h" #include "flatbuffers/stl_emulation.h" -#include "flatbuffers/verifier.h" namespace flatbuffers { @@ -221,79 +220,6 @@ const T* GetSizePrefixedRoot(const void* buf) { return GetRoot(reinterpret_cast(buf) + sizeof(SizeT)); } -// Checked variants of the root access helpers that first run the FlatBuffers -// verifier on the provided buffer. These are intended as secure-by-default -// helpers for callers that are dealing with untrusted input and want a single -// API that combines verification with obtaining the typed root pointer. -// -// If verification fails, these functions return nullptr instead of a typed -// pointer into the buffer. -template -const T* GetRootChecked(const void* buf, size_t len, - const Verifier::Options& opts = Verifier::Options()) { - if (!buf) return nullptr; - Verifier verifier(reinterpret_cast(buf), len, opts); - if (!verifier.VerifyBuffer()) return nullptr; - return GetRoot(buf); -} - -template -const T* GetRootChecked(const void* buf, size_t len, const char* identifier, - const Verifier::Options& opts = Verifier::Options()) { - if (!buf) return nullptr; - Verifier verifier(reinterpret_cast(buf), len, opts); - if (!verifier.VerifyBuffer(identifier)) return nullptr; - return GetRoot(buf); -} - -template -const T* GetSizePrefixedRootChecked( - const void* buf, size_t len, const Verifier::Options& opts = Verifier::Options()) { - if (!buf) return nullptr; - Verifier verifier(reinterpret_cast(buf), len, opts); - if (!verifier.VerifySizePrefixedBuffer(nullptr)) return nullptr; - return GetSizePrefixedRoot(buf); -} - -template -const T* GetSizePrefixedRootChecked( - const void* buf, size_t len, const char* identifier, - const Verifier::Options& opts = Verifier::Options()) { - if (!buf) return nullptr; - Verifier verifier(reinterpret_cast(buf), len, opts); - if (!verifier.VerifySizePrefixedBuffer(identifier)) return nullptr; - return GetSizePrefixedRoot(buf); -} - -template -T* GetMutableRootChecked(void* buf, size_t len, - const Verifier::Options& opts = Verifier::Options()) { - return const_cast( - GetRootChecked(const_cast(buf), len, opts)); -} - -template -T* GetMutableRootChecked(void* buf, size_t len, const char* identifier, - const Verifier::Options& opts = Verifier::Options()) { - return const_cast(GetRootChecked(const_cast(buf), len, - identifier, opts)); -} - -template -T* GetMutableSizePrefixedRootChecked( - void* buf, size_t len, const Verifier::Options& opts = Verifier::Options()) { - return const_cast(GetSizePrefixedRootChecked( - const_cast(buf), len, opts)); -} - -template -T* GetMutableSizePrefixedRootChecked( - void* buf, size_t len, const char* identifier, - const Verifier::Options& opts = Verifier::Options()) { - return const_cast(GetSizePrefixedRootChecked( - const_cast(buf), len, identifier, opts)); -} - } // namespace flatbuffers #endif // FLATBUFFERS_BUFFER_H_ diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 0d9b5ccedb4..5217046f756 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -75,6 +75,85 @@ inline const uint8_t* GetBufferStartFromRootPointer(const void* root) { return nullptr; } +// Checked variants of the root access helpers that first run the FlatBuffers +// verifier on the provided buffer. These are intended as secure-by-default +// helpers for callers that are dealing with untrusted input and want a single +// API that combines verification with obtaining the typed root pointer. +// +// If verification fails, these functions return nullptr instead of a typed +// pointer into the buffer. +template +inline const T* GetRootChecked( + const void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifyBuffer()) return nullptr; + return GetRoot(buf); +} + +template +inline const T* GetRootChecked( + const void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifyBuffer(identifier)) return nullptr; + return GetRoot(buf); +} + +template +inline const T* GetSizePrefixedRootChecked( + const void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifySizePrefixedBuffer(nullptr)) return nullptr; + return GetSizePrefixedRoot(buf); +} + +template +inline const T* GetSizePrefixedRootChecked( + const void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + if (!buf) return nullptr; + Verifier verifier(reinterpret_cast(buf), len, opts); + if (!verifier.VerifySizePrefixedBuffer(identifier)) return nullptr; + return GetSizePrefixedRoot(buf); +} + +template +inline T* GetMutableRootChecked( + void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast( + GetRootChecked(const_cast(buf), len, opts)); +} + +template +inline T* GetMutableRootChecked( + void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetRootChecked(const_cast(buf), len, + identifier, opts)); +} + +template +inline T* GetMutableSizePrefixedRootChecked( + void* buf, size_t len, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetSizePrefixedRootChecked( + const_cast(buf), len, opts)); +} + +template +inline T* GetMutableSizePrefixedRootChecked( + void* buf, size_t len, const char* identifier, + const Verifier::Options& opts = Verifier::Options()) { + return const_cast(GetSizePrefixedRootChecked( + const_cast(buf), len, identifier, opts)); +} + /// @brief This return the prefixed size of a FlatBuffer. template inline SizeT GetPrefixedSize(const uint8_t* buf) {