usecrate::follow::Follow; usecrate::{ForwardsUOffset, SOffsetT, SkipSizePrefix, UOffsetT, VOffsetT, Vector, SIZE_UOFFSET}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use core::ops::Range; use core::option::Option;
#[cfg(not(feature = "std"))] use alloc::borrow::Cow; #[cfg(feature = "std")] use std::borrow::Cow;
#[cfg(all(nightly, not(feature = "std")))] use core::error::Error; #[cfg(feature = "std")] use std::error::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)] pubenum ErrorTraceDetail {
VectorElement { index: usize, position: usize },
TableField { field_name: Cow<'static, str>, position: usize },
UnionVariant { variant: Cow<'static, str>, position: usize },
}
/// 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, Debug, PartialEq, Eq)] pubenum InvalidFlatbuffer {
MissingRequiredField {
required: Cow<'static, str>,
error_trace: ErrorTrace,
},
InconsistentUnion {
field: Cow<'static, str>,
field_type: Cow<'static, str>,
error_trace: ErrorTrace,
},
Utf8Error {
error: core::str::Utf8Error,
range: Range<usize>,
error_trace: ErrorTrace,
},
MissingNullTerminator {
range: Range<usize>,
error_trace: ErrorTrace,
},
Unaligned {
position: usize,
unaligned_type: Cow<'static, str>,
error_trace: ErrorTrace,
},
RangeOutOfBounds {
range: Range<usize>,
error_trace: ErrorTrace,
},
SignedOffsetOutOfBounds {
soffset: SOffsetT,
position: usize,
error_trace: ErrorTrace,
}, // Dos detecting errors. These do not get error traces since it will probably be very large.
TooManyTables,
ApparentSizeTooLarge,
DepthLimitReached,
}
impl core::fmt::Display for InvalidFlatbuffer { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { matchself {
InvalidFlatbuffer::MissingRequiredField { required, error_trace } => {
writeln!(f, "Missing required field `{}`.\n{}", required, error_trace)?;
}
InvalidFlatbuffer::InconsistentUnion { field, field_type, error_trace } => {
writeln!(
f, "Exactly one of union discriminant (`{}`) and value (`{}`) are present.\n{}",
field_type, field, error_trace
)?;
}
InvalidFlatbuffer::Utf8Error { error, range, error_trace } => {
writeln!(f, "Utf8 error for string in {:?}: {}\n{}", range, error, error_trace)?;
}
InvalidFlatbuffer::MissingNullTerminator { range, error_trace } => {
writeln!(
f, "String in range [{}, {}) is missing its null terminator.\n{}",
range.start, range.end, error_trace
)?;
}
InvalidFlatbuffer::Unaligned { position, unaligned_type, error_trace } => {
writeln!(
f, "Type `{}` at position {} is unaligned.\n{}",
unaligned_type, position, error_trace
)?;
}
InvalidFlatbuffer::RangeOutOfBounds { range, error_trace } => {
writeln!(
f, "Range [{}, {}) is out of bounds.\n{}",
range.start, range.end, error_trace
)?;
}
InvalidFlatbuffer::SignedOffsetOutOfBounds { soffset, position, error_trace } => {
writeln!(
f, "Signed offset at position {} has value {} which points out of bounds.\n{}",
position, soffset, error_trace
)?;
}
InvalidFlatbuffer::TooManyTables {} => {
writeln!(f, "Too many tables.")?;
}
InvalidFlatbuffer::ApparentSizeTooLarge {} => {
writeln!(f, "Apparent size too large.")?;
}
InvalidFlatbuffer::DepthLimitReached {} => {
writeln!(f, "Nested table depth limit reached.")?;
}
}
Ok(())
}
}
impl core::fmt::Display for ErrorTrace { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use ErrorTraceDetail::*; for e inself.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(())
}
}
/// 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> { iflet Err(e) = res.as_mut() { use InvalidFlatbuffer::*; iflet 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: Cow<'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)] pubstruct 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?
}
/// Carries the verification state. Should not be reused between tables. #[derive(Debug)] pubstruct Verifier<'opts, 'buf> {
buffer: &'buf [u8],
opts: &'opts VerifierOptions,
depth: usize,
num_tables: usize,
apparent_size: usize,
}
impl<'opts, 'buf> Verifier<'opts, 'buf> { pubfn new(opts: &'opts VerifierOptions, buffer: &'buf [u8]) -> Self { Self { opts, buffer, depth: 0, num_tables: 0, apparent_size: 0 }
} /// Resets verifier internal state. #[inline] pubfn reset(&mutself) { self.depth = 0; self.num_tables = 0; self.num_tables = 0;
} /// Checks `pos` is aligned to T's alignment. This does not mean `buffer[pos]` is aligned w.r.t /// memory since `buffer: &[u8]` has alignment 1. /// /// ### WARNING /// /// This does not work for flatbuffers-structs as they have alignment 1 according to /// `core::mem::align_of` but are meant to have higher alignment within a Flatbuffer w.r.t. /// `buffer[0]`. TODO(caspern). /// /// Note this does not impact soundness as this crate does not assume alignment of structs #[inline] pubfn is_aligned<T>(&self, pos: usize) -> Result<()> { if pos % core::mem::align_of::<T>() == 0 {
Ok(())
} else {
Err(InvalidFlatbuffer::Unaligned {
unaligned_type: Cow::Borrowed(core::any::type_name::<T>()),
position: pos,
error_trace: Default::default(),
})
}
} #[inline] pubfn range_in_buffer(&mutself, 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; ifself.apparent_size > self.opts.max_apparent_size { return Err(InvalidFlatbuffer::ApparentSizeTooLarge);
}
Ok(())
} /// Check that there really is a T in there. #[inline] pubfn in_buffer<T>(&mutself, pos: usize) -> Result<()> { self.is_aligned::<T>(pos)?; self.range_in_buffer(pos, core::mem::size_of::<T>())
} #[inline] pubfn get_u8(&mutself, pos: usize) -> Result<u8> { self.in_buffer::<u8>(pos)?;
Ok(u8::from_le_bytes([self.buffer[pos]]))
} #[inline] fn get_u16(&mutself, pos: usize) -> Result<u16> { self.in_buffer::<u16>(pos)?;
Ok(u16::from_le_bytes([self.buffer[pos], self.buffer[pos + 1]]))
} #[inline] pubfn get_uoffset(&mutself, 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(&mutself, 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.unsigned_abs() as usize)
} else {
pos.checked_add(offset.unsigned_abs() as usize)
}; iflet Some(x) = derefed { if x < self.buffer.len() { return Ok(x);
}
}
Err(InvalidFlatbuffer::SignedOffsetOutOfBounds {
soffset: offset,
position: pos,
error_trace: Default::default(),
})
} #[inline] pubfn 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; ifself.num_tables > self.opts.max_tables { return Err(InvalidFlatbuffer::TooManyTables);
} self.depth += 1; ifself.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. pubfn verify_union_variant<T: Verifiable>(
&mutself,
variant: impl Into<Cow<'static, str>>,
position: usize,
) -> Result<()> { let res = T::run_verifier(self, position);
append_trace(res, ErrorTraceDetail::UnionVariant { variant: variant.into(), 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. pubstruct 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> { pubfn deref(&mutself, 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] pubfn visit_field<T: Verifiable>( mutself,
field_name: impl Into<Cow<'static, str>>,
field: VOffsetT,
required: bool,
) -> Result<Self> { iflet Some(field_pos) = self.deref(field)? {
trace_field(T::run_verifier(self.verifier, field_pos), field_name.into(), field_pos)?; return Ok(self);
} if required {
InvalidFlatbuffer::new_missing_required(field_name.into())
} 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. pubfn visit_union<Key, UnionVerifier>( mutself,
key_field_name: impl Into<Cow<'static, str>>,
key_field_voff: VOffsetT,
val_field_name: impl Into<Cow<'static, str>>,
val_field_voff: VOffsetT,
required: bool,
verify_union: UnionVerifier,
) -> Result<Self> where
Key: Follow<'buf> + Verifiable,
UnionVerifier:
(core::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.into())
} else {
Ok(self)
}
}
(Some(k), Some(v)) => {
trace_field(Key::run_verifier(self.verifier, k), key_field_name.into(), k)?; // Safety: // Run verifier on `k` above let discriminant = unsafe { Key::follow(self.verifier.buffer, k) };
trace_field(
verify_union(discriminant, self.verifier, v),
val_field_name.into(),
v,
)?;
Ok(self)
}
_ => InvalidFlatbuffer::new_inconsistent_union(
key_field_name.into(),
val_field_name.into(),
),
}
} pubfn 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. pubtrait 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<core::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(core::mem::size_of::<T>()); let end = start.saturating_add(size);
v.range_in_buffer(start, size)?;
Ok(core::ops::Range { start, end })
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.