mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 04:04:19 +00:00
Rust Flatbuffers Verifier (#6269)
* Updated comments and fixed a fundemental type error. * bump rust flatbuffers semver * Initial commit with verifier, need to clean up * Verifier tested. Needs clean up and refactoring. * Display for InvalidFlatbuffer and better errors for strings * SimpleToVerify, some refactoring * Combined VerifierType TableAccessorFuncBody into FollowType * scrub todos * Update Rust get_root functions. There are 6 variants, with verifier options, default verifier options and no verification "fast". * Rename root fns * inline * Update to use thiserror * fix for bad compiler * improve error formatting * Replace multiply with saturating_multiply * saturating adds too * Add docs disclaiming experimental verification system Co-authored-by: Casper Neo <cneo@google.com>
This commit is contained in:
@@ -13,3 +13,4 @@ categories = ["encoding", "data-structures", "memory-management"]
|
||||
[dependencies]
|
||||
smallvec = "1.0"
|
||||
bitflags = "1.2"
|
||||
thiserror = "1.0"
|
||||
|
||||
@@ -435,7 +435,7 @@ impl<'fbb> FlatBufferBuilder<'fbb> {
|
||||
// Write the vtable offset, which is the start of any Table.
|
||||
// We fill its value later.
|
||||
let object_revloc_to_vtable: WIPOffset<VTableWIPOffset> =
|
||||
WIPOffset::new(self.push::<UOffsetT>(0xF0F0_F0F0 as UOffsetT).value());
|
||||
WIPOffset::new(self.push::<UOffsetT>(0xF0F0_F0F0).value());
|
||||
|
||||
// Layout of the data this function will create when a new vtable is
|
||||
// needed.
|
||||
|
||||
@@ -27,16 +27,9 @@ use std::marker::PhantomData;
|
||||
/// Writing a new Follow implementation primarily involves deciding whether
|
||||
/// you want to return data (of the type Self::Inner) or do you want to
|
||||
/// continue traversing the FlatBuffer.
|
||||
pub trait Follow<'a> {
|
||||
pub trait Follow<'buf> {
|
||||
type Inner;
|
||||
fn follow(buf: &'a [u8], loc: usize) -> Self::Inner;
|
||||
}
|
||||
|
||||
/// Execute a follow as a top-level function.
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn lifted_follow<'a, T: Follow<'a>>(buf: &'a [u8], loc: usize) -> T::Inner {
|
||||
T::follow(buf, loc)
|
||||
fn follow(buf: &'buf [u8], loc: usize) -> Self::Inner;
|
||||
}
|
||||
|
||||
/// FollowStart wraps a Follow impl in a struct type. This can make certain
|
||||
|
||||
107
rust/flatbuffers/src/get_root.rs
Normal file
107
rust/flatbuffers/src/get_root.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
Follow, ForwardsUOffset, InvalidFlatbuffer, SkipSizePrefix, Verifiable, Verifier,
|
||||
VerifierOptions,
|
||||
};
|
||||
|
||||
/// Gets the root of the Flatbuffer, verifying it first with default options.
|
||||
/// Note that verification is an experimental feature and may not be maximally performant or
|
||||
/// catch every error (though that is the goal). See the `_unchecked` variants for previous
|
||||
/// behavior.
|
||||
pub fn root<'buf, T>(data: &'buf [u8]) -> Result<T::Inner, InvalidFlatbuffer>
|
||||
where
|
||||
T: 'buf + Follow<'buf> + Verifiable,
|
||||
{
|
||||
let opts = VerifierOptions::default();
|
||||
root_with_opts::<T>(&opts, data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Gets the root of the Flatbuffer, verifying it first with given options.
|
||||
/// Note that verification is an experimental feature and may not be maximally performant or
|
||||
/// catch every error (though that is the goal). See the `_unchecked` variants for previous
|
||||
/// behavior.
|
||||
pub fn root_with_opts<'opts, 'buf, T>(
|
||||
opts: &'opts VerifierOptions,
|
||||
data: &'buf [u8],
|
||||
) -> Result<T::Inner, InvalidFlatbuffer>
|
||||
where
|
||||
T: 'buf + Follow<'buf> + Verifiable,
|
||||
{
|
||||
let mut v = Verifier::new(&opts, data);
|
||||
<ForwardsUOffset<T>>::run_verifier(&mut v, 0)?;
|
||||
Ok(unsafe { root_unchecked::<T>(data) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Gets the root of a size prefixed Flatbuffer, verifying it first with default options.
|
||||
/// Note that verification is an experimental feature and may not be maximally performant or
|
||||
/// catch every error (though that is the goal). See the `_unchecked` variants for previous
|
||||
/// behavior.
|
||||
pub fn size_prefixed_root<'buf, T>(data: &'buf [u8]) -> Result<T::Inner, InvalidFlatbuffer>
|
||||
where
|
||||
T: 'buf + Follow<'buf> + Verifiable,
|
||||
{
|
||||
let opts = VerifierOptions::default();
|
||||
size_prefixed_root_with_opts::<T>(&opts, data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Gets the root of a size prefixed Flatbuffer, verifying it first with given options.
|
||||
/// Note that verification is an experimental feature and may not be maximally performant or
|
||||
/// catch every error (though that is the goal). See the `_unchecked` variants for previous
|
||||
/// behavior.
|
||||
pub fn size_prefixed_root_with_opts<'opts, 'buf, T>(
|
||||
opts: &'opts VerifierOptions,
|
||||
data: &'buf [u8],
|
||||
) -> Result<T::Inner, InvalidFlatbuffer>
|
||||
where
|
||||
T: 'buf + Follow<'buf> + Verifiable,
|
||||
{
|
||||
let mut v = Verifier::new(&opts, data);
|
||||
<SkipSizePrefix<ForwardsUOffset<T>>>::run_verifier(&mut v, 0)?;
|
||||
Ok(unsafe { size_prefixed_root_unchecked::<T>(data) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Gets root for a trusted Flatbuffer.
|
||||
/// # Safety
|
||||
/// Flatbuffers accessors do not perform validation checks before accessing. Unlike the other
|
||||
/// `root` functions, this does not validate the flatbuffer before returning the accessor. Users
|
||||
/// must trust `data` contains a valid flatbuffer (e.g. b/c it was built by your software). Reading
|
||||
/// unchecked buffers may cause panics or even UB.
|
||||
pub unsafe fn root_unchecked<'buf, T>(data: &'buf [u8]) -> T::Inner
|
||||
where
|
||||
T: Follow<'buf> + 'buf,
|
||||
{
|
||||
<ForwardsUOffset<T>>::follow(data, 0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Gets root for a trusted, size prefixed, Flatbuffer.
|
||||
/// # Safety
|
||||
/// Flatbuffers accessors do not perform validation checks before accessing. Unlike the other
|
||||
/// `root` functions, this does not validate the flatbuffer before returning the accessor. Users
|
||||
/// must trust `data` contains a valid flatbuffer (e.g. b/c it was built by your software). Reading
|
||||
/// unchecked buffers may cause panics or even UB.
|
||||
pub unsafe fn size_prefixed_root_unchecked<'buf, T>(data: &'buf [u8]) -> T::Inner
|
||||
where
|
||||
T: Follow<'buf> + 'buf,
|
||||
{
|
||||
<SkipSizePrefix<ForwardsUOffset<T>>>::follow(data, 0)
|
||||
}
|
||||
@@ -19,26 +19,27 @@
|
||||
//! A library for memory-efficient serialization of data.
|
||||
//!
|
||||
//! This crate provides runtime support for the FlatBuffers format in the Rust programming language.
|
||||
//! To use this crate, first generate code with the `flatc` compiler, as described here: https://google.github.io/flatbuffers/
|
||||
//! To use this crate, first generate code with the `flatc` compiler, as described here: <https://google.github.io/flatbuffers/>
|
||||
//! Then, include that code into your project.
|
||||
//! Finally, add this crate to your `Cargo.toml`.
|
||||
//!
|
||||
//! At this time, Rust support is experimental, and APIs may change between minor versions.
|
||||
//!
|
||||
//! At this time, to generate Rust code, you will need the latest `master` version of `flatc`, available from here: https://github.com/google/flatbuffers
|
||||
//! At this time, to generate Rust code, you will need the latest `master` version of `flatc`, available from here: <https://github.com/google/flatbuffers>
|
||||
//! (On OSX, you can install FlatBuffers from `HEAD` with the Homebrew package manager.)
|
||||
|
||||
mod builder;
|
||||
mod endian_scalar;
|
||||
mod follow;
|
||||
mod get_root;
|
||||
mod primitives;
|
||||
mod push;
|
||||
mod table;
|
||||
mod vector;
|
||||
mod verifier;
|
||||
mod vtable;
|
||||
mod vtable_writer;
|
||||
|
||||
pub use bitflags;
|
||||
pub use crate::builder::FlatBufferBuilder;
|
||||
pub use crate::endian_scalar::{
|
||||
byte_swap_f32, byte_swap_f64, emplace_scalar, read_scalar, read_scalar_at, EndianScalar,
|
||||
@@ -46,9 +47,15 @@ pub use crate::endian_scalar::{
|
||||
pub use crate::follow::{Follow, FollowStart};
|
||||
pub use crate::primitives::*;
|
||||
pub use crate::push::Push;
|
||||
pub use crate::table::{buffer_has_identifier, get_root, get_size_prefixed_root, Table};
|
||||
pub use crate::table::{buffer_has_identifier, Table};
|
||||
pub use crate::vector::{follow_cast_ref, SafeSliceAccess, Vector, VectorIter};
|
||||
pub use crate::verifier::{
|
||||
ErrorTraceDetail, InvalidFlatbuffer, SimpleToVerifyInSlice, Verifiable, Verifier,
|
||||
VerifierOptions,
|
||||
};
|
||||
pub use crate::vtable::field_index_to_field_offset;
|
||||
pub use bitflags;
|
||||
pub use get_root::*;
|
||||
|
||||
// TODO(rw): Unify `create_vector` and `create_vector_direct` by using
|
||||
// `Into<Vector<...>>`.
|
||||
|
||||
@@ -55,14 +55,6 @@ impl<'a> Follow<'a> for Table<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_root<'a, T: Follow<'a> + 'a>(data: &'a [u8]) -> T::Inner {
|
||||
<ForwardsUOffset<T>>::follow(data, 0)
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_size_prefixed_root<'a, T: Follow<'a> + 'a>(data: &'a [u8]) -> T::Inner {
|
||||
<SkipSizePrefix<ForwardsUOffset<T>>>::follow(data, 0)
|
||||
}
|
||||
#[inline]
|
||||
pub fn buffer_has_identifier(data: &[u8], ident: &str, size_prefixed: bool) -> bool {
|
||||
assert_eq!(ident.len(), FILE_IDENTIFIER_LENGTH);
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
use std::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::slice::from_raw_parts;
|
||||
use std::str::from_utf8_unchecked;
|
||||
use std::fmt::{Debug, Result, Formatter};
|
||||
|
||||
use crate::endian_scalar::read_scalar_at;
|
||||
#[cfg(target_endian = "little")]
|
||||
@@ -32,14 +32,13 @@ pub struct Vector<'a, T: 'a>(&'a [u8], usize, PhantomData<T>);
|
||||
impl<'a, T> Debug for Vector<'a, T>
|
||||
where
|
||||
T: 'a + Follow<'a>,
|
||||
<T as Follow<'a>>::Inner : Debug
|
||||
<T as Follow<'a>>::Inner: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
f.debug_list().entries(self.iter()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We cannot use derive for these two impls, as it would only implement Copy
|
||||
// and Clone for `T: Copy` and `T: Clone` respectively. However `Vector<'a, T>`
|
||||
// can always be copied, no matter that `T` you have.
|
||||
@@ -104,6 +103,8 @@ impl SafeSliceAccess for u8 {}
|
||||
impl SafeSliceAccess for i8 {}
|
||||
impl SafeSliceAccess for bool {}
|
||||
|
||||
// TODO(caspern): Get rid of this. Conditional compliation is unnecessary complexity.
|
||||
// Vectors of primitives just don't work on big endian machines!!!
|
||||
#[cfg(target_endian = "little")]
|
||||
mod le_safe_slice_impls {
|
||||
impl super::SafeSliceAccess for u16 {}
|
||||
|
||||
559
rust/flatbuffers/src/verifier.rs
Normal file
559
rust/flatbuffers/src/verifier.rs
Normal file
@@ -0,0 +1,559 @@
|
||||
use crate::follow::Follow;
|
||||
use crate::{ForwardsUOffset, SOffsetT, SkipSizePrefix, UOffsetT, VOffsetT, Vector, SIZE_UOFFSET};
|
||||
use std::ops::Range;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Traces the location of data errors. Not populated for Dos detecting errors.
|
||||
/// Useful for MissingRequiredField and Utf8Error in particular, though
|
||||
/// the other errors should not be producible by correct flatbuffers implementations.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorTraceDetail {
|
||||
VectorElement {
|
||||
index: usize,
|
||||
position: usize,
|
||||
},
|
||||
TableField {
|
||||
field_name: &'static str,
|
||||
position: usize,
|
||||
},
|
||||
UnionVariant {
|
||||
variant: &'static str,
|
||||
position: usize,
|
||||
},
|
||||
}
|
||||
#[derive(PartialEq, Eq, Default, Debug, Clone)]
|
||||
pub struct ErrorTrace(Vec<ErrorTraceDetail>);
|
||||
impl std::convert::AsRef<[ErrorTraceDetail]> for ErrorTrace {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[ErrorTraceDetail] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes how a flatuffer is invalid and, for data errors, roughly where. No extra tracing
|
||||
/// information is given for DoS detecting errors since it will probably be a lot.
|
||||
#[derive(Clone, Error, Debug, PartialEq, Eq)]
|
||||
pub enum InvalidFlatbuffer {
|
||||
#[error("Missing required field `{required}`.\n{error_trace}")]
|
||||
MissingRequiredField {
|
||||
required: &'static str,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("Union exactly one of union discriminant (`{field_type}`) and value \
|
||||
(`{field}`) are present.\n{error_trace}")]
|
||||
InconsistentUnion {
|
||||
field: &'static str,
|
||||
field_type: &'static str,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("Utf8 error for string in {range:?}: {error}\n{error_trace}")]
|
||||
Utf8Error {
|
||||
#[source]
|
||||
error: std::str::Utf8Error,
|
||||
range: Range<usize>,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("String in range [{}, {}) is missing its null terminator.\n{error_trace}",
|
||||
range.start, range.end)]
|
||||
MissingNullTerminator {
|
||||
range: Range<usize>,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("Type `{unaligned_type}` at position {position} is unaligned.\n{error_trace}")]
|
||||
Unaligned {
|
||||
position: usize,
|
||||
unaligned_type: &'static str,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("Range [{}, {}) is out of bounds.\n{error_trace}", range.start, range.end)]
|
||||
RangeOutOfBounds {
|
||||
range: Range<usize>,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
#[error("Signed offset at position {position} has value {soffset} which points out of bounds.\
|
||||
\n{error_trace}")]
|
||||
SignedOffsetOutOfBounds {
|
||||
soffset: SOffsetT,
|
||||
position: usize,
|
||||
error_trace: ErrorTrace,
|
||||
},
|
||||
// Dos detecting errors. These do not get error traces since it will probably be very large.
|
||||
#[error("Too many tables.")]
|
||||
TooManyTables,
|
||||
#[error("Apparent size too large.")]
|
||||
ApparentSizeTooLarge,
|
||||
#[error("Nested table depth limit reached.")]
|
||||
DepthLimitReached,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ErrorTrace {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use ErrorTraceDetail::*;
|
||||
for e in self.0.iter() {
|
||||
match e {
|
||||
VectorElement { index, position } => {
|
||||
writeln!(
|
||||
f,
|
||||
"\twhile verifying vector element {:?} at position {:?}",
|
||||
index, position
|
||||
)?;
|
||||
}
|
||||
TableField {
|
||||
field_name,
|
||||
position,
|
||||
} => {
|
||||
writeln!(
|
||||
f,
|
||||
"\twhile verifying table field `{}` at position {:?}",
|
||||
field_name, position
|
||||
)?;
|
||||
}
|
||||
UnionVariant { variant, position } => {
|
||||
writeln!(
|
||||
f,
|
||||
"\t while verifying union variant `{}` at position {:?}",
|
||||
variant, position
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::prelude::v1::Result<T, InvalidFlatbuffer>;
|
||||
|
||||
impl InvalidFlatbuffer {
|
||||
fn new_range_oob<T>(start: usize, end: usize) -> Result<T> {
|
||||
Err(Self::RangeOutOfBounds {
|
||||
range: Range { start, end },
|
||||
error_trace: Default::default(),
|
||||
})
|
||||
}
|
||||
fn new_inconsistent_union<T>(field: &'static str, field_type: &'static str) -> Result<T> {
|
||||
Err(Self::InconsistentUnion {
|
||||
field,
|
||||
field_type,
|
||||
error_trace: Default::default(),
|
||||
})
|
||||
}
|
||||
fn new_missing_required<T>(required: &'static str) -> Result<T> {
|
||||
Err(Self::MissingRequiredField {
|
||||
required,
|
||||
error_trace: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Records the path to the verifier detail if the error is a data error and not a DoS error.
|
||||
fn append_trace<T>(mut res: Result<T>, d: ErrorTraceDetail) -> Result<T> {
|
||||
if let Err(e) = res.as_mut() {
|
||||
use InvalidFlatbuffer::*;
|
||||
if let MissingRequiredField { error_trace, .. }
|
||||
| Unaligned { error_trace, .. }
|
||||
| RangeOutOfBounds { error_trace, .. }
|
||||
| InconsistentUnion { error_trace, .. }
|
||||
| Utf8Error { error_trace, .. }
|
||||
| MissingNullTerminator { error_trace, .. }
|
||||
| SignedOffsetOutOfBounds { error_trace, .. } = e
|
||||
{
|
||||
error_trace.0.push(d)
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Adds a TableField trace detail if `res` is a data error.
|
||||
fn trace_field<T>(res: Result<T>, field_name: &'static str, position: usize) -> Result<T> {
|
||||
append_trace(
|
||||
res,
|
||||
ErrorTraceDetail::TableField {
|
||||
field_name,
|
||||
position,
|
||||
},
|
||||
)
|
||||
}
|
||||
/// Adds a TableField trace detail if `res` is a data error.
|
||||
fn trace_elem<T>(res: Result<T>, index: usize, position: usize) -> Result<T> {
|
||||
append_trace(res, ErrorTraceDetail::VectorElement { index, position })
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VerifierOptions {
|
||||
/// Maximum depth of nested tables allowed in a valid flatbuffer.
|
||||
pub max_depth: usize,
|
||||
/// Maximum number of tables allowed in a valid flatbuffer.
|
||||
pub max_tables: usize,
|
||||
/// Maximum "apparent" size of the message if the Flatbuffer object DAG is expanded into a
|
||||
/// tree.
|
||||
pub max_apparent_size: usize,
|
||||
/// Ignore errors where a string is missing its null terminator.
|
||||
/// This is mostly a problem if the message will be sent to a client using old c-strings.
|
||||
pub ignore_missing_null_terminator: bool,
|
||||
// probably want an option to ignore utf8 errors since strings come from c++
|
||||
// options to error un-recognized enums and unions? possible footgun.
|
||||
// Ignore nested flatbuffers, etc?
|
||||
}
|
||||
impl Default for VerifierOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_depth: 64,
|
||||
max_tables: 1_000_000,
|
||||
// size_ might do something different.
|
||||
max_apparent_size: 1 << 31,
|
||||
ignore_missing_null_terminator: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Carries the verification state. Should not be reused between tables.
|
||||
#[derive(Debug)]
|
||||
pub struct Verifier<'opts, 'buf> {
|
||||
buffer: &'buf [u8],
|
||||
opts: &'opts VerifierOptions,
|
||||
depth: usize,
|
||||
num_tables: usize,
|
||||
apparent_size: usize,
|
||||
}
|
||||
impl<'opts, 'buf> Verifier<'opts, 'buf> {
|
||||
pub fn new(opts: &'opts VerifierOptions, buffer: &'buf [u8]) -> Self {
|
||||
Self {
|
||||
opts,
|
||||
buffer,
|
||||
depth: 0,
|
||||
num_tables: 0,
|
||||
apparent_size: 0,
|
||||
}
|
||||
}
|
||||
/// Resets verifier internal state.
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.depth = 0;
|
||||
self.num_tables = 0;
|
||||
self.num_tables = 0;
|
||||
}
|
||||
/// Check that there really is a T in there.
|
||||
#[inline]
|
||||
fn is_aligned<T>(&self, pos: usize) -> Result<()> {
|
||||
// Safe because we're not dereferencing.
|
||||
let p = unsafe { self.buffer.as_ptr().add(pos) };
|
||||
if (p as usize) % std::mem::align_of::<T>() == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InvalidFlatbuffer::Unaligned {
|
||||
unaligned_type: std::any::type_name::<T>(),
|
||||
position: pos,
|
||||
error_trace: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn range_in_buffer(&mut self, pos: usize, size: usize) -> Result<()> {
|
||||
let end = pos.saturating_add(size);
|
||||
if end > self.buffer.len() {
|
||||
return InvalidFlatbuffer::new_range_oob(pos, end);
|
||||
}
|
||||
self.apparent_size += size;
|
||||
if self.apparent_size > self.opts.max_apparent_size {
|
||||
return Err(InvalidFlatbuffer::ApparentSizeTooLarge);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn in_buffer<T>(&mut self, pos: usize) -> Result<()> {
|
||||
self.is_aligned::<T>(pos)?;
|
||||
self.range_in_buffer(pos, std::mem::size_of::<T>())
|
||||
}
|
||||
#[inline]
|
||||
fn get_u16(&mut self, pos: usize) -> Result<u16> {
|
||||
self.in_buffer::<u16>(pos)?;
|
||||
Ok(u16::from_le_bytes([self.buffer[pos], self.buffer[pos + 1]]))
|
||||
}
|
||||
#[inline]
|
||||
fn get_uoffset(&mut self, pos: usize) -> Result<UOffsetT> {
|
||||
self.in_buffer::<u32>(pos)?;
|
||||
Ok(u32::from_le_bytes([
|
||||
self.buffer[pos],
|
||||
self.buffer[pos + 1],
|
||||
self.buffer[pos + 2],
|
||||
self.buffer[pos + 3],
|
||||
]))
|
||||
}
|
||||
#[inline]
|
||||
fn deref_soffset(&mut self, pos: usize) -> Result<usize> {
|
||||
self.in_buffer::<SOffsetT>(pos)?;
|
||||
let offset = SOffsetT::from_le_bytes([
|
||||
self.buffer[pos],
|
||||
self.buffer[pos + 1],
|
||||
self.buffer[pos + 2],
|
||||
self.buffer[pos + 3],
|
||||
]);
|
||||
|
||||
// signed offsets are subtracted.
|
||||
let derefed = if offset > 0 {
|
||||
pos.checked_sub(offset.abs() as usize)
|
||||
} else {
|
||||
pos.checked_add(offset.abs() as usize)
|
||||
};
|
||||
if let Some(x) = derefed {
|
||||
if x < self.buffer.len() {
|
||||
return Ok(x);
|
||||
}
|
||||
}
|
||||
Err(InvalidFlatbuffer::SignedOffsetOutOfBounds {
|
||||
soffset: offset,
|
||||
position: pos,
|
||||
error_trace: Default::default(),
|
||||
})
|
||||
}
|
||||
#[inline]
|
||||
pub fn visit_table<'ver>(
|
||||
&'ver mut self,
|
||||
table_pos: usize,
|
||||
) -> Result<TableVerifier<'ver, 'opts, 'buf>> {
|
||||
let vtable_pos = self.deref_soffset(table_pos)?;
|
||||
let vtable_len = self.get_u16(vtable_pos)? as usize;
|
||||
self.is_aligned::<VOffsetT>(vtable_pos.saturating_add(vtable_len))?; // i.e. vtable_len is even.
|
||||
self.range_in_buffer(vtable_pos, vtable_len)?;
|
||||
// Check bounds.
|
||||
self.num_tables += 1;
|
||||
if self.num_tables > self.opts.max_tables {
|
||||
return Err(InvalidFlatbuffer::TooManyTables);
|
||||
}
|
||||
self.depth += 1;
|
||||
if self.depth > self.opts.max_depth {
|
||||
return Err(InvalidFlatbuffer::DepthLimitReached);
|
||||
}
|
||||
Ok(TableVerifier {
|
||||
pos: table_pos,
|
||||
vtable: vtable_pos,
|
||||
vtable_len,
|
||||
verifier: self,
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the union variant's type's verifier assuming the variant is at the given position,
|
||||
/// tracing the error.
|
||||
pub fn verify_union_variant<T: Verifiable>(
|
||||
&mut self,
|
||||
variant: &'static str,
|
||||
position: usize,
|
||||
) -> Result<()> {
|
||||
let res = T::run_verifier(self, position);
|
||||
append_trace(res, ErrorTraceDetail::UnionVariant { variant, position })
|
||||
}
|
||||
}
|
||||
|
||||
// Cache table metadata in usize so we don't have to cast types or jump around so much.
|
||||
// We will visit every field anyway.
|
||||
pub struct TableVerifier<'ver, 'opts, 'buf> {
|
||||
// Absolute position of table in buffer
|
||||
pos: usize,
|
||||
// Absolute position of vtable in buffer.
|
||||
vtable: usize,
|
||||
// Length of vtable.
|
||||
vtable_len: usize,
|
||||
// Verifier struct which holds the surrounding state and options.
|
||||
verifier: &'ver mut Verifier<'opts, 'buf>,
|
||||
}
|
||||
impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
|
||||
fn deref(&mut self, field: VOffsetT) -> Result<Option<usize>> {
|
||||
let field = field as usize;
|
||||
if field < self.vtable_len {
|
||||
let field_offset = self.verifier.get_u16(self.vtable.saturating_add(field))?;
|
||||
if field_offset > 0 {
|
||||
// Field is present.
|
||||
let field_pos = self.pos.saturating_add(field_offset as usize);
|
||||
return Ok(Some(field_pos));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn visit_field<T: Verifiable>(
|
||||
mut self,
|
||||
field_name: &'static str,
|
||||
field: VOffsetT,
|
||||
required: bool,
|
||||
) -> Result<Self> {
|
||||
if let Some(field_pos) = self.deref(field)? {
|
||||
trace_field(
|
||||
T::run_verifier(self.verifier, field_pos),
|
||||
field_name,
|
||||
field_pos,
|
||||
)?;
|
||||
return Ok(self);
|
||||
}
|
||||
if required {
|
||||
InvalidFlatbuffer::new_missing_required(field_name)
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
/// Union verification is complicated. The schemas passes this function the metadata of the
|
||||
/// union's key (discriminant) and value fields, and a callback. The function verifies and
|
||||
/// reads the key, then invokes the callback to perform data-dependent verification.
|
||||
pub fn visit_union<Key, UnionVerifier>(
|
||||
mut self,
|
||||
key_field_name: &'static str,
|
||||
key_field_voff: VOffsetT,
|
||||
val_field_name: &'static str,
|
||||
val_field_voff: VOffsetT,
|
||||
required: bool,
|
||||
verify_union: UnionVerifier,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Key: Follow<'buf> + Verifiable,
|
||||
UnionVerifier:
|
||||
(std::ops::FnOnce(<Key as Follow<'buf>>::Inner, &mut Verifier, usize) -> Result<()>),
|
||||
// NOTE: <Key as Follow<'buf>>::Inner == Key
|
||||
{
|
||||
// TODO(caspern): how to trace vtable errors?
|
||||
let val_pos = self.deref(val_field_voff)?;
|
||||
let key_pos = self.deref(key_field_voff)?;
|
||||
match (key_pos, val_pos) {
|
||||
(None, None) => {
|
||||
if required {
|
||||
InvalidFlatbuffer::new_missing_required(val_field_name)
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
(Some(k), Some(v)) => {
|
||||
trace_field(Key::run_verifier(self.verifier, k), key_field_name, k)?;
|
||||
let discriminant = Key::follow(self.verifier.buffer, k);
|
||||
trace_field(
|
||||
verify_union(discriminant, self.verifier, v),
|
||||
val_field_name,
|
||||
v,
|
||||
)?;
|
||||
Ok(self)
|
||||
}
|
||||
_ => InvalidFlatbuffer::new_inconsistent_union(key_field_name, val_field_name),
|
||||
}
|
||||
}
|
||||
pub fn finish(self) -> &'ver mut Verifier<'opts, 'buf> {
|
||||
self.verifier.depth -= 1;
|
||||
self.verifier
|
||||
}
|
||||
}
|
||||
|
||||
// Needs to be implemented for Tables and maybe structs.
|
||||
// Unions need some special treatment.
|
||||
pub trait Verifiable {
|
||||
/// Runs the verifier for this type, assuming its at position `pos` in the verifier's buffer.
|
||||
/// Should not need to be called directly.
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()>;
|
||||
}
|
||||
|
||||
// Verify the uoffset and then pass verifier to the type being pointed to.
|
||||
impl<T: Verifiable> Verifiable for ForwardsUOffset<T> {
|
||||
#[inline]
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
|
||||
let offset = v.get_uoffset(pos)? as usize;
|
||||
let next_pos = offset.saturating_add(pos);
|
||||
T::run_verifier(v, next_pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks and returns the range containing the flatbuffers vector.
|
||||
fn verify_vector_range<T>(v: &mut Verifier, pos: usize) -> Result<std::ops::Range<usize>> {
|
||||
let len = v.get_uoffset(pos)? as usize;
|
||||
let start = pos.saturating_add(SIZE_UOFFSET);
|
||||
v.is_aligned::<T>(start)?;
|
||||
let size = len.saturating_mul(std::mem::size_of::<T>());
|
||||
let end = start.saturating_add(size);
|
||||
v.range_in_buffer(start, size)?;
|
||||
Ok(std::ops::Range { start, end })
|
||||
}
|
||||
|
||||
pub trait SimpleToVerifyInSlice {}
|
||||
impl SimpleToVerifyInSlice for bool {}
|
||||
impl SimpleToVerifyInSlice for i8 {}
|
||||
impl SimpleToVerifyInSlice for u8 {}
|
||||
impl SimpleToVerifyInSlice for i16 {}
|
||||
impl SimpleToVerifyInSlice for u16 {}
|
||||
impl SimpleToVerifyInSlice for i32 {}
|
||||
impl SimpleToVerifyInSlice for u32 {}
|
||||
impl SimpleToVerifyInSlice for f32 {}
|
||||
impl SimpleToVerifyInSlice for i64 {}
|
||||
impl SimpleToVerifyInSlice for u64 {}
|
||||
impl SimpleToVerifyInSlice for f64 {}
|
||||
|
||||
impl<T: SimpleToVerifyInSlice> Verifiable for Vector<'_, T> {
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
|
||||
verify_vector_range::<T>(v, pos)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Verifiable> Verifiable for SkipSizePrefix<T> {
|
||||
#[inline]
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
|
||||
T::run_verifier(v, pos.saturating_add(crate::SIZE_SIZEPREFIX))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Verifiable> Verifiable for Vector<'_, ForwardsUOffset<T>> {
|
||||
#[inline]
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
|
||||
let range = verify_vector_range::<ForwardsUOffset<T>>(v, pos)?;
|
||||
let size = std::mem::size_of::<ForwardsUOffset<T>>();
|
||||
for (i, element_pos) in range.step_by(size).enumerate() {
|
||||
trace_elem(
|
||||
<ForwardsUOffset<T>>::run_verifier(v, element_pos),
|
||||
i,
|
||||
element_pos,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Verifiable for &'a str {
|
||||
#[inline]
|
||||
fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
|
||||
let range = verify_vector_range::<u8>(v, pos)?;
|
||||
let has_null_terminator = v.buffer.get(range.end).map(|&b| b == 0).unwrap_or(false);
|
||||
let s = std::str::from_utf8(&v.buffer[range.clone()]);
|
||||
if let Err(error) = s {
|
||||
return Err(InvalidFlatbuffer::Utf8Error {
|
||||
error,
|
||||
range,
|
||||
error_trace: Default::default(),
|
||||
});
|
||||
}
|
||||
if !v.opts.ignore_missing_null_terminator && !has_null_terminator {
|
||||
return Err(InvalidFlatbuffer::MissingNullTerminator {
|
||||
range,
|
||||
error_trace: Default::default(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Verify VectorOfTables, Unions, Arrays, Structs...
|
||||
macro_rules! impl_verifiable_for {
|
||||
($T: ty) => {
|
||||
impl Verifiable for $T {
|
||||
#[inline]
|
||||
fn run_verifier<'opts, 'buf>(v: &mut Verifier<'opts, 'buf>, pos: usize) -> Result<()> {
|
||||
v.in_buffer::<$T>(pos)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_verifiable_for!(bool);
|
||||
impl_verifiable_for!(u8);
|
||||
impl_verifiable_for!(i8);
|
||||
impl_verifiable_for!(u16);
|
||||
impl_verifiable_for!(i16);
|
||||
impl_verifiable_for!(u32);
|
||||
impl_verifiable_for!(i32);
|
||||
impl_verifiable_for!(f32);
|
||||
impl_verifiable_for!(u64);
|
||||
impl_verifiable_for!(i64);
|
||||
impl_verifiable_for!(f64);
|
||||
Reference in New Issue
Block a user