Optional verification system for validating FlatBuffer integrity, detecting corruption, and ensuring safe access to potentially untrusted data. The verification system provides configurable security checks and detailed error reporting.
The main verification class that validates FlatBuffer structure and content according to configurable security policies.
template<bool TrackVerifierBufferSize = false>
class VerifierTemplate {
public:
/**
* Verification options structure
*/
struct Options {
/** Maximum nesting depth allowed (default: 64) */
uoffset_t max_depth = 64;
/** Maximum number of tables allowed (default: 1000000) */
uoffset_t max_tables = 1000000;
/** Whether to check data alignment (default: true) */
bool check_alignment = true;
/** Whether to verify nested flatbuffers (default: true) */
bool check_nested_flatbuffers = true;
/** Maximum buffer size allowed (default: FLATBUFFERS_MAX_BUFFER_SIZE) */
size_t max_size = FLATBUFFERS_MAX_BUFFER_SIZE;
/** Use assertions for errors instead of returning false (default: false) */
bool assert = false;
};
/**
* Constructor for buffer verification
* @param buf Pointer to buffer data
* @param buf_len Buffer length in bytes
* @param opts Verification options
*/
VerifierTemplate(const uint8_t *buf, size_t buf_len, const Options &opts = {});
/**
* Central verification checkpoint - all checks go through this
* @param ok Boolean result of specific check
* @return True if check passed, false otherwise
*/
bool Check(const bool ok) const;
/**
* Verify memory range is within buffer bounds
* @param elem Starting position
* @param elem_len Length of data to verify
* @return True if range is valid
*/
bool Verify(const size_t elem, const size_t elem_len) const;
/**
* Verify data alignment requirements
* @param elem Data position
* @param align Required alignment (power of 2)
* @return True if properly aligned
*/
bool VerifyAlignment(const size_t elem, const size_t align) const;
/**
* Verify offset points within buffer
* @param start Starting position
* @param offset Offset value to check
* @return True if offset is valid
*/
bool VerifyOffset(const size_t start, const uoffset_t offset) const;
/**
* Verify 64-bit offset points within buffer
* @param start Starting position
* @param offset 64-bit offset value to check
* @return True if offset is valid
*/
bool VerifyOffset64(const size_t start, const uoffset64_t offset) const;
/**
* Verify table structure and vtable
* @param table Pointer to table
* @return True if table is valid
*/
template<typename T>
bool VerifyTable(const T *table);
/**
* Verify table with custom vtable size
* @param table Pointer to table
* @param vtable_size Expected vtable size
* @return True if table is valid
*/
bool VerifyTableStart(const uint8_t *table, const voffset_t vtable_size);
/**
* Verify vector structure and elements
* @param vec Pointer to vector
* @return True if vector is valid
*/
template<typename T>
bool VerifyVector(const Vector<T> *vec) const;
/**
* Verify vector with element count
* @param vec Pointer to vector
* @param elem_size Size of each element
* @return True if vector is valid
*/
bool VerifyVectorOrString(const uint8_t *vec, const size_t elem_size) const;
/**
* Verify string structure and UTF-8 content
* @param str Pointer to string
* @return True if string is valid
*/
bool VerifyString(const String *str) const;
/**
* Verify buffer contains valid root table
* @param identifier Optional file identifier to check
* @return True if buffer has valid root
*/
template<typename T>
bool VerifyBuffer(const char *identifier = nullptr);
/**
* Verify size-prefixed buffer
* @param identifier Optional file identifier to check
* @return True if size-prefixed buffer is valid
*/
template<typename T>
bool VerifySizePrefixedBuffer(const char *identifier = nullptr);
/**
* Get number of tables verified (if tracking enabled)
* @return Table count
*/
uoffset_t GetComputedSize() const;
};
// Standard verifier type alias
using Verifier = VerifierTemplate<false>;Basic Usage Example:
#include "flatbuffers/flatbuffers.h"
#include "monster_generated.h" // Generated from schema
bool VerifyMonsterBuffer(const uint8_t *buf, size_t size) {
// Create verifier with default options
flatbuffers::Verifier verifier(buf, size);
// Verify the buffer contains a valid Monster
return verifier.VerifyBuffer<Monster>(nullptr);
}
// With custom options
bool VerifyMonsterBufferStrict(const uint8_t *buf, size_t size) {
flatbuffers::Verifier::Options opts;
opts.max_depth = 32; // Stricter depth limit
opts.max_tables = 10000; // Fewer tables allowed
opts.check_alignment = true; // Strict alignment checking
opts.assert = false; // Don't use assertions
flatbuffers::Verifier verifier(buf, size, opts);
return verifier.VerifyBuffer<Monster>("MONS"); // With file identifier
}Detailed configuration options for customizing verification behavior based on security requirements.
struct VerifierOptions {
/**
* Maximum nesting depth for tables and vectors
* Prevents stack overflow attacks via deeply nested structures
* Default: 64, Range: 1-1000
*/
uoffset_t max_depth = 64;
/**
* Maximum number of tables that can be traversed
* Prevents DOS attacks via excessive table references
* Default: 1000000, Range: 1-UINT32_MAX
*/
uoffset_t max_tables = 1000000;
/**
* Whether to verify proper data alignment
* Ensures data is aligned according to type requirements
* Default: true
*/
bool check_alignment = true;
/**
* Whether to recursively verify nested FlatBuffers
* Verifies buffers stored as byte vectors
* Default: true
*/
bool check_nested_flatbuffers = true;
/**
* Maximum total buffer size allowed
* Prevents excessive memory usage
* Default: FLATBUFFERS_MAX_BUFFER_SIZE (2GB-1)
*/
size_t max_size = FLATBUFFERS_MAX_BUFFER_SIZE;
/**
* Use assertions instead of returning false
* When true, failed checks trigger assertions (debug builds)
* Default: false (return false on failure)
*/
bool assert = false;
};Typical patterns for integrating verification into applications with various security requirements.
// Network data verification (strict security)
bool VerifyNetworkData(const uint8_t *data, size_t len) {
flatbuffers::Verifier::Options opts;
opts.max_depth = 16; // Shallow nesting only
opts.max_tables = 1000; // Limited table count
opts.check_alignment = true; // Strict alignment
opts.max_size = 1024 * 1024; // 1MB limit
flatbuffers::Verifier verifier(data, len, opts);
return verifier.VerifyBuffer<NetworkMessage>("NETM");
}
// File data verification (moderate security)
bool VerifyFileData(const uint8_t *data, size_t len) {
flatbuffers::Verifier::Options opts;
opts.max_depth = 64; // Default depth
opts.max_tables = 100000; // Reasonable limit
opts.check_alignment = true; // Check alignment
opts.max_size = 100 * 1024 * 1024; // 100MB limit
flatbuffers::Verifier verifier(data, len, opts);
return verifier.VerifyBuffer<FileData>("FILE");
}
// Trusted data verification (basic checks)
bool VerifyTrustedData(const uint8_t *data, size_t len) {
flatbuffers::Verifier::Options opts;
opts.max_depth = 128; // Allow deeper nesting
opts.max_tables = 1000000; // Default limit
opts.check_alignment = false; // Skip alignment checks
flatbuffers::Verifier verifier(data, len, opts);
return verifier.VerifyBuffer<TrustedData>();
}
// Size-prefixed buffer verification
bool VerifySizePrefixed(const uint8_t *data, size_t len) {
flatbuffers::Verifier verifier(data, len);
return verifier.VerifySizePrefixedBuffer<RootTable>("SPFX");
}Verification APIs adapted for different programming languages while maintaining consistent security guarantees.
JavaScript/TypeScript:
import { ByteBuffer } from 'flatbuffers';
interface VerificationOptions {
maxDepth?: number;
maxTables?: number;
checkAlignment?: boolean;
maxSize?: number;
}
function verifyBuffer(
buffer: Uint8Array,
options: VerificationOptions = {}
): boolean {
// Basic JavaScript verification implementation
if (buffer.length < 4) return false;
const bb = new ByteBuffer(buffer);
const rootOffset = bb.readInt32(0);
// Additional checks based on options
return rootOffset > 0 && rootOffset < buffer.length;
}Python:
import flatbuffers
class VerificationOptions:
def __init__(self, max_depth=64, max_tables=1000000,
check_alignment=True, max_size=2**31-1):
self.max_depth = max_depth
self.max_tables = max_tables
self.check_alignment = check_alignment
self.max_size = max_size
def verify_buffer(buffer: bytes, options: VerificationOptions = None) -> bool:
"""
Verify FlatBuffer integrity
Args:
buffer: Buffer to verify
options: Verification options
Returns:
bool: True if buffer is valid
"""
if options is None:
options = VerificationOptions()
# Basic validation
if len(buffer) < 4 or len(buffer) > options.max_size:
return False
try:
# Check root offset
root_offset = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buffer, 0)
return 0 < root_offset < len(buffer)
except (struct.error, IndexError):
return FalseJava:
import com.google.flatbuffers.*;
import java.nio.ByteBuffer;
public class VerificationOptions {
public int maxDepth = 64;
public int maxTables = 1000000;
public boolean checkAlignment = true;
public long maxSize = Integer.MAX_VALUE;
}
public static boolean verifyBuffer(ByteBuffer buffer, VerificationOptions options) {
if (options == null) {
options = new VerificationOptions();
}
// Basic validation
if (buffer.remaining() < 4 || buffer.remaining() > options.maxSize) {
return false;
}
try {
buffer.order(ByteOrder.LITTLE_ENDIAN);
int rootOffset = buffer.getInt(buffer.position());
return rootOffset > 0 && rootOffset < buffer.remaining();
} catch (Exception e) {
return false;
}
}Go:
package flatbuffers
type VerificationOptions struct {
MaxDepth int
MaxTables int
CheckAlignment bool
MaxSize int64
}
func DefaultVerificationOptions() *VerificationOptions {
return &VerificationOptions{
MaxDepth: 64,
MaxTables: 1000000,
CheckAlignment: true,
MaxSize: 2147483647, // 2GB-1
}
}
func VerifyBuffer(buf []byte, opts *VerificationOptions) bool {
if opts == nil {
opts = DefaultVerificationOptions()
}
// Basic validation
if len(buf) < 4 || int64(len(buf)) > opts.MaxSize {
return false
}
// Check root offset
rootOffset := GetUOffsetT(buf[0:4])
return rootOffset > 0 && int(rootOffset) < len(buf)
}Rust:
use flatbuffers::{InvalidFlatbuffer, Verifiable, Verifier, VerifierOptions};
#[derive(Debug, Clone)]
pub struct CustomVerifierOptions {
pub max_depth: usize,
pub max_tables: usize,
pub check_alignment: bool,
pub max_size: usize,
}
impl Default for CustomVerifierOptions {
fn default() -> Self {
Self {
max_depth: 64,
max_tables: 1_000_000,
check_alignment: true,
max_size: i32::MAX as usize,
}
}
}
pub fn verify_buffer<T: Verifiable>(
buf: &[u8],
opts: Option<CustomVerifierOptions>
) -> Result<(), InvalidFlatbuffer> {
let opts = opts.unwrap_or_default();
// Basic validation
if buf.len() < 4 || buf.len() > opts.max_size {
return Err(InvalidFlatbuffer::BufferTooSmall);
}
// Create verifier with custom options
let verifier_opts = VerifierOptions {
max_depth: opts.max_depth,
max_tables: opts.max_tables,
check_alignment: opts.check_alignment,
..Default::default()
};
let mut verifier = Verifier::new(&verifier_opts, buf);
T::run_verifier(&mut verifier, 0)
}Comprehensive error reporting for verification failures with actionable diagnostic information.
// Enhanced verification with detailed error reporting
class DetailedVerifier : public flatbuffers::Verifier {
public:
struct VerificationError {
enum Type {
BUFFER_TOO_SMALL,
BUFFER_TOO_LARGE,
INVALID_OFFSET,
ALIGNMENT_ERROR,
DEPTH_EXCEEDED,
TABLE_LIMIT_EXCEEDED,
INVALID_VTABLE,
INVALID_STRING,
INVALID_VECTOR,
NESTED_BUFFER_ERROR
};
Type type;
size_t position;
std::string description;
VerificationError(Type t, size_t pos, const std::string& desc)
: type(t), position(pos), description(desc) {}
};
private:
mutable std::vector<VerificationError> errors_;
public:
DetailedVerifier(const uint8_t *buf, size_t buf_len, const Options &opts = {})
: flatbuffers::Verifier(buf, buf_len, opts) {}
bool Check(const bool ok) const override {
if (!ok && errors_.empty()) {
// Record the first error encountered
errors_.emplace_back(
VerificationError::BUFFER_TOO_SMALL,
0,
"Verification check failed"
);
}
return flatbuffers::Verifier::Check(ok);
}
const std::vector<VerificationError>& GetErrors() const {
return errors_;
}
std::string GetErrorReport() const {
std::string report = "Verification Errors:\n";
for (const auto& error : errors_) {
report += " - " + error.description +
" at position " + std::to_string(error.position) + "\n";
}
return report;
}
};
// Usage with detailed error reporting
bool VerifyWithDiagnostics(const uint8_t *buf, size_t size) {
DetailedVerifier verifier(buf, size);
bool result = verifier.VerifyBuffer<Monster>();
if (!result) {
std::cout << verifier.GetErrorReport() << std::endl;
for (const auto& error : verifier.GetErrors()) {
std::cout << "Error type: " << error.type
<< " at position: " << error.position << std::endl;
}
}
return result;
}Complete Verification Example:
#include "flatbuffers/flatbuffers.h"
#include "monster_generated.h"
#include <iostream>
#include <fstream>
#include <vector>
// Comprehensive buffer verification function
bool VerifyMonsterFile(const std::string& filename) {
// Read file
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Cannot open file: " << filename << std::endl;
return false;
}
// Get file size
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
// Read data
std::vector<uint8_t> buffer(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
// Configure verification options
flatbuffers::Verifier::Options opts;
opts.max_depth = 32; // Moderate depth limit
opts.max_tables = 10000; // Reasonable table limit
opts.check_alignment = true; // Verify alignment
opts.max_size = 10 * 1024 * 1024; // 10MB limit
// Verify buffer
flatbuffers::Verifier verifier(buffer.data(), size, opts);
bool isValid = verifier.VerifyBuffer<Monster>("MONS");
if (isValid) {
std::cout << "Buffer verification passed" << std::endl;
// Safe to access data
const Monster* monster = GetMonster(buffer.data());
std::cout << "Monster name: " << monster->name()->c_str() << std::endl;
std::cout << "Monster HP: " << monster->hp() << std::endl;
} else {
std::cerr << "Buffer verification failed!" << std::endl;
}
return isValid;
}
int main() {
return VerifyMonsterFile("monster.bin") ? 0 : 1;
}