blob: d5e31df5d176c98d64e85e6c174bcbeddf43671d [file] [log] [blame]
James Kuszmaul8e62b022022-03-22 09:33:25 -07001#[cfg(feature = "no_std")]
2use alloc::vec::Vec;
3use core::ops::Range;
4use core::option::Option;
5use crate::follow::Follow;
6use crate::{ForwardsUOffset, SOffsetT, SkipSizePrefix, UOffsetT, VOffsetT, Vector, SIZE_UOFFSET};
7
8#[cfg(feature="no_std")]
9extern crate thiserror_core2 as thiserror;
10use thiserror::Error;
11
12/// Traces the location of data errors. Not populated for Dos detecting errors.
13/// Useful for MissingRequiredField and Utf8Error in particular, though
14/// the other errors should not be producible by correct flatbuffers implementations.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum ErrorTraceDetail {
17 VectorElement {
18 index: usize,
19 position: usize,
20 },
21 TableField {
22 field_name: &'static str,
23 position: usize,
24 },
25 UnionVariant {
26 variant: &'static str,
27 position: usize,
28 },
29}
30#[derive(PartialEq, Eq, Default, Debug, Clone)]
31pub struct ErrorTrace(Vec<ErrorTraceDetail>);
32impl core::convert::AsRef<[ErrorTraceDetail]> for ErrorTrace {
33 #[inline]
34 fn as_ref(&self) -> &[ErrorTraceDetail] {
35 &self.0
36 }
37}
38
39/// Describes how a flatuffer is invalid and, for data errors, roughly where. No extra tracing
40/// information is given for DoS detecting errors since it will probably be a lot.
41#[derive(Clone, Error, Debug, PartialEq, Eq)]
42pub enum InvalidFlatbuffer {
43 #[error("Missing required field `{required}`.\n{error_trace}")]
44 MissingRequiredField {
45 required: &'static str,
46 error_trace: ErrorTrace,
47 },
48 #[error(
49 "Union exactly one of union discriminant (`{field_type}`) and value \
50 (`{field}`) are present.\n{error_trace}"
51 )]
52 InconsistentUnion {
53 field: &'static str,
54 field_type: &'static str,
55 error_trace: ErrorTrace,
56 },
57 #[error("Utf8 error for string in {range:?}: {error}\n{error_trace}")]
58 Utf8Error {
59 #[source]
60 error: core::str::Utf8Error,
61 range: Range<usize>,
62 error_trace: ErrorTrace,
63 },
64 #[error("String in range [{}, {}) is missing its null terminator.\n{error_trace}",
65 range.start, range.end)]
66 MissingNullTerminator {
67 range: Range<usize>,
68 error_trace: ErrorTrace,
69 },
70 #[error("Type `{unaligned_type}` at position {position} is unaligned.\n{error_trace}")]
71 Unaligned {
72 position: usize,
73 unaligned_type: &'static str,
74 error_trace: ErrorTrace,
75 },
76 #[error("Range [{}, {}) is out of bounds.\n{error_trace}", range.start, range.end)]
77 RangeOutOfBounds {
78 range: Range<usize>,
79 error_trace: ErrorTrace,
80 },
81 #[error(
82 "Signed offset at position {position} has value {soffset} which points out of bounds.\
83 \n{error_trace}"
84 )]
85 SignedOffsetOutOfBounds {
86 soffset: SOffsetT,
87 position: usize,
88 error_trace: ErrorTrace,
89 },
90 // Dos detecting errors. These do not get error traces since it will probably be very large.
91 #[error("Too many tables.")]
92 TooManyTables,
93 #[error("Apparent size too large.")]
94 ApparentSizeTooLarge,
95 #[error("Nested table depth limit reached.")]
96 DepthLimitReached,
97}
98
99impl core::fmt::Display for ErrorTrace {
100 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
101 use ErrorTraceDetail::*;
102 for e in self.0.iter() {
103 match e {
104 VectorElement { index, position } => {
105 writeln!(
106 f,
107 "\twhile verifying vector element {:?} at position {:?}",
108 index, position
109 )?;
110 }
111 TableField {
112 field_name,
113 position,
114 } => {
115 writeln!(
116 f,
117 "\twhile verifying table field `{}` at position {:?}",
118 field_name, position
119 )?;
120 }
121 UnionVariant { variant, position } => {
122 writeln!(
123 f,
124 "\t while verifying union variant `{}` at position {:?}",
125 variant, position
126 )?;
127 }
128 }
129 }
130 Ok(())
131 }
132}
133
134pub type Result<T> = core::result::Result<T, InvalidFlatbuffer>;
135
136impl InvalidFlatbuffer {
137 fn new_range_oob<T>(start: usize, end: usize) -> Result<T> {
138 Err(Self::RangeOutOfBounds {
139 range: Range { start, end },
140 error_trace: Default::default(),
141 })
142 }
143 fn new_inconsistent_union<T>(field: &'static str, field_type: &'static str) -> Result<T> {
144 Err(Self::InconsistentUnion {
145 field,
146 field_type,
147 error_trace: Default::default(),
148 })
149 }
150 fn new_missing_required<T>(required: &'static str) -> Result<T> {
151 Err(Self::MissingRequiredField {
152 required,
153 error_trace: Default::default(),
154 })
155 }
156}
157
158/// Records the path to the verifier detail if the error is a data error and not a DoS error.
159fn append_trace<T>(mut res: Result<T>, d: ErrorTraceDetail) -> Result<T> {
160 if let Err(e) = res.as_mut() {
161 use InvalidFlatbuffer::*;
162 if let MissingRequiredField { error_trace, .. }
163 | Unaligned { error_trace, .. }
164 | RangeOutOfBounds { error_trace, .. }
165 | InconsistentUnion { error_trace, .. }
166 | Utf8Error { error_trace, .. }
167 | MissingNullTerminator { error_trace, .. }
168 | SignedOffsetOutOfBounds { error_trace, .. } = e
169 {
170 error_trace.0.push(d)
171 }
172 }
173 res
174}
175
176/// Adds a TableField trace detail if `res` is a data error.
177fn trace_field<T>(res: Result<T>, field_name: &'static str, position: usize) -> Result<T> {
178 append_trace(
179 res,
180 ErrorTraceDetail::TableField {
181 field_name,
182 position,
183 },
184 )
185}
186/// Adds a TableField trace detail if `res` is a data error.
187fn trace_elem<T>(res: Result<T>, index: usize, position: usize) -> Result<T> {
188 append_trace(res, ErrorTraceDetail::VectorElement { index, position })
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct VerifierOptions {
193 /// Maximum depth of nested tables allowed in a valid flatbuffer.
194 pub max_depth: usize,
195 /// Maximum number of tables allowed in a valid flatbuffer.
196 pub max_tables: usize,
197 /// Maximum "apparent" size of the message if the Flatbuffer object DAG is expanded into a
198 /// tree.
199 pub max_apparent_size: usize,
200 /// Ignore errors where a string is missing its null terminator.
201 /// This is mostly a problem if the message will be sent to a client using old c-strings.
202 pub ignore_missing_null_terminator: bool,
203 // probably want an option to ignore utf8 errors since strings come from c++
204 // options to error un-recognized enums and unions? possible footgun.
205 // Ignore nested flatbuffers, etc?
206}
207impl Default for VerifierOptions {
208 fn default() -> Self {
209 Self {
210 max_depth: 64,
211 max_tables: 1_000_000,
212 // size_ might do something different.
213 max_apparent_size: 1 << 31,
214 ignore_missing_null_terminator: false,
215 }
216 }
217}
218
219/// Carries the verification state. Should not be reused between tables.
220#[derive(Debug)]
221pub struct Verifier<'opts, 'buf> {
222 buffer: &'buf [u8],
223 opts: &'opts VerifierOptions,
224 depth: usize,
225 num_tables: usize,
226 apparent_size: usize,
227}
228impl<'opts, 'buf> Verifier<'opts, 'buf> {
229 pub fn new(opts: &'opts VerifierOptions, buffer: &'buf [u8]) -> Self {
230 Self {
231 opts,
232 buffer,
233 depth: 0,
234 num_tables: 0,
235 apparent_size: 0,
236 }
237 }
238 /// Resets verifier internal state.
239 #[inline]
240 pub fn reset(&mut self) {
241 self.depth = 0;
242 self.num_tables = 0;
243 self.num_tables = 0;
244 }
245 /// Checks `pos` is aligned to T's alignment. This does not mean `buffer[pos]` is aligned w.r.t
246 /// memory since `buffer: &[u8]` has alignment 1.
247 ///
248 /// ### WARNING
249 /// This does not work for flatbuffers-structs as they have alignment 1 according to
250 /// `core::mem::align_of` but are meant to have higher alignment within a Flatbuffer w.r.t.
251 /// `buffer[0]`. TODO(caspern).
252 #[inline]
253 fn is_aligned<T>(&self, pos: usize) -> Result<()> {
254 if pos % core::mem::align_of::<T>() == 0 {
255 Ok(())
256 } else {
257 Err(InvalidFlatbuffer::Unaligned {
258 unaligned_type: core::any::type_name::<T>(),
259 position: pos,
260 error_trace: Default::default(),
261 })
262 }
263 }
264 #[inline]
265 fn range_in_buffer(&mut self, pos: usize, size: usize) -> Result<()> {
266 let end = pos.saturating_add(size);
267 if end > self.buffer.len() {
268 return InvalidFlatbuffer::new_range_oob(pos, end);
269 }
270 self.apparent_size += size;
271 if self.apparent_size > self.opts.max_apparent_size {
272 return Err(InvalidFlatbuffer::ApparentSizeTooLarge);
273 }
274 Ok(())
275 }
276 /// Check that there really is a T in there.
277 #[inline]
278 pub fn in_buffer<T>(&mut self, pos: usize) -> Result<()> {
279 self.is_aligned::<T>(pos)?;
280 self.range_in_buffer(pos, core::mem::size_of::<T>())
281 }
282 #[inline]
283 fn get_u16(&mut self, pos: usize) -> Result<u16> {
284 self.in_buffer::<u16>(pos)?;
285 Ok(u16::from_le_bytes([self.buffer[pos], self.buffer[pos + 1]]))
286 }
287 #[inline]
288 fn get_uoffset(&mut self, pos: usize) -> Result<UOffsetT> {
289 self.in_buffer::<u32>(pos)?;
290 Ok(u32::from_le_bytes([
291 self.buffer[pos],
292 self.buffer[pos + 1],
293 self.buffer[pos + 2],
294 self.buffer[pos + 3],
295 ]))
296 }
297 #[inline]
298 fn deref_soffset(&mut self, pos: usize) -> Result<usize> {
299 self.in_buffer::<SOffsetT>(pos)?;
300 let offset = SOffsetT::from_le_bytes([
301 self.buffer[pos],
302 self.buffer[pos + 1],
303 self.buffer[pos + 2],
304 self.buffer[pos + 3],
305 ]);
306
307 // signed offsets are subtracted.
308 let derefed = if offset > 0 {
309 pos.checked_sub(offset.abs() as usize)
310 } else {
311 pos.checked_add(offset.abs() as usize)
312 };
313 if let Some(x) = derefed {
314 if x < self.buffer.len() {
315 return Ok(x);
316 }
317 }
318 Err(InvalidFlatbuffer::SignedOffsetOutOfBounds {
319 soffset: offset,
320 position: pos,
321 error_trace: Default::default(),
322 })
323 }
324 #[inline]
325 pub fn visit_table<'ver>(
326 &'ver mut self,
327 table_pos: usize,
328 ) -> Result<TableVerifier<'ver, 'opts, 'buf>> {
329 let vtable_pos = self.deref_soffset(table_pos)?;
330 let vtable_len = self.get_u16(vtable_pos)? as usize;
331 self.is_aligned::<VOffsetT>(vtable_pos.saturating_add(vtable_len))?; // i.e. vtable_len is even.
332 self.range_in_buffer(vtable_pos, vtable_len)?;
333 // Check bounds.
334 self.num_tables += 1;
335 if self.num_tables > self.opts.max_tables {
336 return Err(InvalidFlatbuffer::TooManyTables);
337 }
338 self.depth += 1;
339 if self.depth > self.opts.max_depth {
340 return Err(InvalidFlatbuffer::DepthLimitReached);
341 }
342 Ok(TableVerifier {
343 pos: table_pos,
344 vtable: vtable_pos,
345 vtable_len,
346 verifier: self,
347 })
348 }
349
350 /// Runs the union variant's type's verifier assuming the variant is at the given position,
351 /// tracing the error.
352 pub fn verify_union_variant<T: Verifiable>(
353 &mut self,
354 variant: &'static str,
355 position: usize,
356 ) -> Result<()> {
357 let res = T::run_verifier(self, position);
358 append_trace(res, ErrorTraceDetail::UnionVariant { variant, position })
359 }
360}
361
362// Cache table metadata in usize so we don't have to cast types or jump around so much.
363// We will visit every field anyway.
364pub struct TableVerifier<'ver, 'opts, 'buf> {
365 // Absolute position of table in buffer
366 pos: usize,
367 // Absolute position of vtable in buffer.
368 vtable: usize,
369 // Length of vtable.
370 vtable_len: usize,
371 // Verifier struct which holds the surrounding state and options.
372 verifier: &'ver mut Verifier<'opts, 'buf>,
373}
374impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
375 fn deref(&mut self, field: VOffsetT) -> Result<Option<usize>> {
376 let field = field as usize;
377 if field < self.vtable_len {
378 let field_offset = self.verifier.get_u16(self.vtable.saturating_add(field))?;
379 if field_offset > 0 {
380 // Field is present.
381 let field_pos = self.pos.saturating_add(field_offset as usize);
382 return Ok(Some(field_pos));
383 }
384 }
385 Ok(None)
386 }
387
388 #[inline]
389 pub fn visit_field<T: Verifiable>(
390 mut self,
391 field_name: &'static str,
392 field: VOffsetT,
393 required: bool,
394 ) -> Result<Self> {
395 if let Some(field_pos) = self.deref(field)? {
396 trace_field(
397 T::run_verifier(self.verifier, field_pos),
398 field_name,
399 field_pos,
400 )?;
401 return Ok(self);
402 }
403 if required {
404 InvalidFlatbuffer::new_missing_required(field_name)
405 } else {
406 Ok(self)
407 }
408 }
409 #[inline]
410 /// Union verification is complicated. The schemas passes this function the metadata of the
411 /// union's key (discriminant) and value fields, and a callback. The function verifies and
412 /// reads the key, then invokes the callback to perform data-dependent verification.
413 pub fn visit_union<Key, UnionVerifier>(
414 mut self,
415 key_field_name: &'static str,
416 key_field_voff: VOffsetT,
417 val_field_name: &'static str,
418 val_field_voff: VOffsetT,
419 required: bool,
420 verify_union: UnionVerifier,
421 ) -> Result<Self>
422 where
423 Key: Follow<'buf> + Verifiable,
424 UnionVerifier:
425 (core::ops::FnOnce(<Key as Follow<'buf>>::Inner, &mut Verifier, usize) -> Result<()>),
426 // NOTE: <Key as Follow<'buf>>::Inner == Key
427 {
428 // TODO(caspern): how to trace vtable errors?
429 let val_pos = self.deref(val_field_voff)?;
430 let key_pos = self.deref(key_field_voff)?;
431 match (key_pos, val_pos) {
432 (None, None) => {
433 if required {
434 InvalidFlatbuffer::new_missing_required(val_field_name)
435 } else {
436 Ok(self)
437 }
438 }
439 (Some(k), Some(v)) => {
440 trace_field(Key::run_verifier(self.verifier, k), key_field_name, k)?;
441 let discriminant = Key::follow(self.verifier.buffer, k);
442 trace_field(
443 verify_union(discriminant, self.verifier, v),
444 val_field_name,
445 v,
446 )?;
447 Ok(self)
448 }
449 _ => InvalidFlatbuffer::new_inconsistent_union(key_field_name, val_field_name),
450 }
451 }
452 pub fn finish(self) -> &'ver mut Verifier<'opts, 'buf> {
453 self.verifier.depth -= 1;
454 self.verifier
455 }
456}
457
458// Needs to be implemented for Tables and maybe structs.
459// Unions need some special treatment.
460pub trait Verifiable {
461 /// Runs the verifier for this type, assuming its at position `pos` in the verifier's buffer.
462 /// Should not need to be called directly.
463 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()>;
464}
465
466// Verify the uoffset and then pass verifier to the type being pointed to.
467impl<T: Verifiable> Verifiable for ForwardsUOffset<T> {
468 #[inline]
469 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
470 let offset = v.get_uoffset(pos)? as usize;
471 let next_pos = offset.saturating_add(pos);
472 T::run_verifier(v, next_pos)
473 }
474}
475
476/// Checks and returns the range containing the flatbuffers vector.
477fn verify_vector_range<T>(v: &mut Verifier, pos: usize) -> Result<core::ops::Range<usize>> {
478 let len = v.get_uoffset(pos)? as usize;
479 let start = pos.saturating_add(SIZE_UOFFSET);
480 v.is_aligned::<T>(start)?;
481 let size = len.saturating_mul(core::mem::size_of::<T>());
482 let end = start.saturating_add(size);
483 v.range_in_buffer(start, size)?;
484 Ok(core::ops::Range { start, end })
485}
486
487pub trait SimpleToVerifyInSlice {}
488impl SimpleToVerifyInSlice for bool {}
489impl SimpleToVerifyInSlice for i8 {}
490impl SimpleToVerifyInSlice for u8 {}
491impl SimpleToVerifyInSlice for i16 {}
492impl SimpleToVerifyInSlice for u16 {}
493impl SimpleToVerifyInSlice for i32 {}
494impl SimpleToVerifyInSlice for u32 {}
495impl SimpleToVerifyInSlice for f32 {}
496impl SimpleToVerifyInSlice for i64 {}
497impl SimpleToVerifyInSlice for u64 {}
498impl SimpleToVerifyInSlice for f64 {}
499
500impl<T: SimpleToVerifyInSlice> Verifiable for Vector<'_, T> {
501 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
502 verify_vector_range::<T>(v, pos)?;
503 Ok(())
504 }
505}
506
507impl<T: Verifiable> Verifiable for SkipSizePrefix<T> {
508 #[inline]
509 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
510 T::run_verifier(v, pos.saturating_add(crate::SIZE_SIZEPREFIX))
511 }
512}
513
514impl<T: Verifiable> Verifiable for Vector<'_, ForwardsUOffset<T>> {
515 #[inline]
516 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
517 let range = verify_vector_range::<ForwardsUOffset<T>>(v, pos)?;
518 let size = core::mem::size_of::<ForwardsUOffset<T>>();
519 for (i, element_pos) in range.step_by(size).enumerate() {
520 trace_elem(
521 <ForwardsUOffset<T>>::run_verifier(v, element_pos),
522 i,
523 element_pos,
524 )?;
525 }
526 Ok(())
527 }
528}
529
530impl<'a> Verifiable for &'a str {
531 #[inline]
532 fn run_verifier(v: &mut Verifier, pos: usize) -> Result<()> {
533 let range = verify_vector_range::<u8>(v, pos)?;
534 let has_null_terminator = v.buffer.get(range.end).map(|&b| b == 0).unwrap_or(false);
535 let s = core::str::from_utf8(&v.buffer[range.clone()]);
536 if let Err(error) = s {
537 return Err(InvalidFlatbuffer::Utf8Error {
538 error,
539 range,
540 error_trace: Default::default(),
541 });
542 }
543 if !v.opts.ignore_missing_null_terminator && !has_null_terminator {
544 return Err(InvalidFlatbuffer::MissingNullTerminator {
545 range,
546 error_trace: Default::default(),
547 });
548 }
549 Ok(())
550 }
551}
552
553// Verify VectorOfTables, Unions, Arrays, Structs...
554macro_rules! impl_verifiable_for {
555 ($T: ty) => {
556 impl Verifiable for $T {
557 #[inline]
558 fn run_verifier<'opts, 'buf>(v: &mut Verifier<'opts, 'buf>, pos: usize) -> Result<()> {
559 v.in_buffer::<$T>(pos)
560 }
561 }
562 };
563}
564impl_verifiable_for!(bool);
565impl_verifiable_for!(u8);
566impl_verifiable_for!(i8);
567impl_verifiable_for!(u16);
568impl_verifiable_for!(i16);
569impl_verifiable_for!(u32);
570impl_verifiable_for!(i32);
571impl_verifiable_for!(f32);
572impl_verifiable_for!(u64);
573impl_verifiable_for!(i64);
574impl_verifiable_for!(f64);