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