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