Skip to content

FlexBuffers: enforce string NUL terminator in verifier (out of bounds read) - #9476

Open
kemrec wants to merge 1 commit into
google:masterfrom
kemrec:flexbuffers-verify-string-terminator
Open

kemrec wants to merge 1 commit into
google:masterfrom
kemrec:flexbuffers-verify-string-terminator

Conversation

@kemrec

@kemrec kemrec commented Nov 12, 2026

Copy link
Copy Markdown

Problem

The FlexBuffers verifier checks that a string occupies size + 1 bytes inside the buffer but never checks that the trailing byte is a NUL. A FlexBuffer whose string length prefix is honest but whose terminator byte is non zero still passes flexbuffers::VerifyBuffer. Because flexbuffers::String::c_str() is a public accessor that callers treat as a C string, any consumer that NUL scans the verified string (strlen, strcmp, printf("%s"), std::string from c_str()) reads past the end of the buffer allocation.

This is the same class as the recently merged VerifyKey fix (commit a6979fe). That change corrected key verification; the string value path through VerifyTerminator was left incomplete. flatbuffers::Verifier::VerifyString already enforces the terminator (Check(buf_[end] == '\0')), so this aligns FlexBuffers with the existing FlatBuffers behavior.

Affected code

include/flatbuffers/flexbuffers.h, VerifyTerminator (reached from VerifyRef for FBT_STRING):

bool VerifyTerminator(const String& s) {
  return VerifyFromPointer(reinterpret_cast<const uint8_t*>(s.c_str()),
                           s.size() + 1);
}

VerifyFromPointer(..., s.size() + 1) only range checks that size + 1 bytes are inside the buffer. It does not check the value of the last byte. By contrast VerifyKey scans for a NUL and returns false when none is found, so keys are safe; the string value path is the outlier.

Impact

Out of bounds read on a buffer that the application already verified. The verifier is the sole security boundary for FlexBuffers: once VerifyBuffer returns true, accessors read with no further bounds checks. A consumer that only uses size() or string_view is unaffected; a consumer that NUL scans c_str() reads out of bounds, which exposes adjacent memory or crashes. c_str() is the natural string accessor, so the exposure is broad. Numeric accessors such as AsInt64 stop at the non numeric tail and are not affected.

Fix

After the range check succeeds, require the terminating byte to be NUL. The range check runs first and guarantees the byte at index size() is in bounds, so reading it is safe:

bool VerifyTerminator(const String& s) {
  return VerifyFromPointer(reinterpret_cast<const uint8_t*>(s.c_str()),
                           s.size() + 1) &&
         s.c_str()[s.size()] == '\0';
}

Reproduction

Built a legitimate FlexBuffer containing a one character string with flexbuffers::Builder, flipped the in range terminator byte from 0x00 to 0x42, confirmed VerifyBuffer still returned true, then called strlen on the verified c_str().

Six byte buffer, built then one byte patched:

01 41 00 02 14 01
         ^^ terminator, patched to 42

Before the fix, AddressSanitizer reports a heap buffer overflow READ at the strlen call. After the fix:

  • Corrupted buffer: VerifyBuffer returns false, no ASAN error.
  • Valid buffer (string "hello"): VerifyBuffer returns true, c_str() reads "hello", length 5, no regression.

VerifyTerminator range-checked size + 1 bytes but did not confirm the
trailing byte is a NUL. A FlexBuffer with an honest length prefix and a
non zero terminator passed VerifyBuffer, so a consumer that NUL scans the
verified String::c_str() (strlen, strcmp, printf %s, std::string from
c_str) read past the end of the buffer.

Require the terminator to be NUL after the range check, matching
flatbuffers::Verifier::VerifyString. Same class as the VerifyKey fix in
a6979fe.
@kemrec
kemrec requested a review from dbaileychess as a code owner November 12, 2026 03:53
@github-actions github-actions Bot added the c++ label Nov 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant