flatbuffers/
verifier.rs

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