Skip to main content

rkyv/validation/archive/
validator.rs

1use core::{
2    alloc::Layout, error::Error, fmt, marker::PhantomData, num::NonZeroUsize,
3    ops::Range,
4};
5
6use rancor::{fail, OptionExt, Source};
7
8use crate::validation::ArchiveContext;
9
10const PTR_WIDTH: usize = (usize::BITS / 4 + 2) as usize;
11
12struct Pointer(pub usize);
13
14impl fmt::Display for Pointer {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "{:#0w$x}", self.0, w = PTR_WIDTH)
17    }
18}
19
20#[derive(Debug)]
21struct UnalignedPointer {
22    address: usize,
23    align: usize,
24}
25
26impl fmt::Display for UnalignedPointer {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(
29            f,
30            "unaligned pointer: ptr {} unaligned for alignment {}",
31            Pointer(self.address),
32            self.align,
33        )
34    }
35}
36
37impl Error for UnalignedPointer {}
38
39#[derive(Debug)]
40struct InvalidSubtreePointer {
41    address: usize,
42    size: usize,
43    subtree_range: Range<usize>,
44}
45
46impl fmt::Display for InvalidSubtreePointer {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(
49            f,
50            "subtree pointer overran range: ptr {} size {} in range {}..{}",
51            Pointer(self.address),
52            self.size,
53            Pointer(self.subtree_range.start),
54            Pointer(self.subtree_range.end),
55        )
56    }
57}
58
59impl Error for InvalidSubtreePointer {}
60
61#[derive(Debug)]
62struct ExceededMaximumSubtreeDepth;
63
64impl fmt::Display for ExceededMaximumSubtreeDepth {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(
67            f,
68            "pushed a subtree range that exceeded the maximum subtree depth",
69        )
70    }
71}
72
73impl Error for ExceededMaximumSubtreeDepth {}
74
75#[derive(Debug)]
76struct RangePoppedTooManyTimes;
77
78impl fmt::Display for RangePoppedTooManyTimes {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "subtree range popped too many times")
81    }
82}
83
84impl Error for RangePoppedTooManyTimes {}
85
86#[derive(Debug)]
87struct RangePoppedOutOfOrder;
88
89impl fmt::Display for RangePoppedOutOfOrder {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "subtree range popped out of order")
92    }
93}
94
95impl Error for RangePoppedOutOfOrder {}
96
97/// A validator that can verify archives with nonlocal memory.
98#[derive(Debug)]
99pub struct ArchiveValidator<'a> {
100    subtree_range: Range<usize>,
101    max_subtree_depth: Option<NonZeroUsize>,
102    _phantom: PhantomData<&'a [u8]>,
103}
104
105impl<'a> ArchiveValidator<'a> {
106    /// Creates a new bounds validator for the given bytes.
107    #[inline]
108    pub fn new(bytes: &'a [u8]) -> Self {
109        Self::with_max_depth(bytes, None)
110    }
111
112    /// Crates a new bounds validator for the given bytes with a maximum
113    /// validation depth.
114    #[inline]
115    pub fn with_max_depth(
116        bytes: &'a [u8],
117        max_subtree_depth: Option<NonZeroUsize>,
118    ) -> Self {
119        let Range { start, end } = bytes.as_ptr_range();
120        Self {
121            subtree_range: Range {
122                start: start as usize,
123                end: end as usize,
124            },
125            max_subtree_depth,
126            _phantom: PhantomData,
127        }
128    }
129}
130
131unsafe impl<E: Source> ArchiveContext<E> for ArchiveValidator<'_> {
132    fn check_subtree_ptr(
133        &mut self,
134        ptr: *const u8,
135        layout: &Layout,
136    ) -> Result<(), E> {
137        let start = ptr as usize;
138        let end = ptr.wrapping_add(layout.size()) as usize;
139        if end < start
140            || start < self.subtree_range.start
141            || end > self.subtree_range.end
142        {
143            fail!(InvalidSubtreePointer {
144                address: start,
145                size: layout.size(),
146                subtree_range: self.subtree_range.clone(),
147            });
148        } else if start & (layout.align() - 1) != 0 {
149            fail!(UnalignedPointer {
150                address: ptr as usize,
151                align: layout.align(),
152            });
153        } else {
154            Ok(())
155        }
156    }
157
158    unsafe fn push_subtree_range(
159        &mut self,
160        root: *const u8,
161        end: *const u8,
162    ) -> Result<Range<usize>, E> {
163        if let Some(max_subtree_depth) = &mut self.max_subtree_depth {
164            *max_subtree_depth = NonZeroUsize::new(max_subtree_depth.get() - 1)
165                .into_trace(ExceededMaximumSubtreeDepth)?;
166        }
167
168        let result = Range {
169            start: end as usize,
170            end: self.subtree_range.end,
171        };
172        self.subtree_range.end = root as usize;
173        Ok(result)
174    }
175
176    unsafe fn pop_subtree_range(
177        &mut self,
178        range: Range<usize>,
179    ) -> Result<(), E> {
180        if range.start < self.subtree_range.end {
181            fail!(RangePoppedOutOfOrder);
182        }
183        self.subtree_range = range;
184        if let Some(max_subtree_depth) = &mut self.max_subtree_depth {
185            *max_subtree_depth = max_subtree_depth
186                .checked_add(1)
187                .into_trace(RangePoppedTooManyTimes)?;
188        }
189        Ok(())
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use rancor::Error;
196
197    use crate::{
198        api::low::{access, access_pos},
199        boxed::ArchivedBox,
200        option::ArchivedOption,
201        util::Align,
202        Archived,
203    };
204
205    #[test]
206    fn basic_functionality() {
207        #[cfg(all(feature = "pointer_width_16", not(feature = "big_endian")))]
208        // Synthetic archive (correct)
209        let synthetic_buf = Align([
210            // "Hello world"
211            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
212            0u8, // padding to 2-alignment
213            1u8, 0u8, // Some + padding
214            0xf2u8, 0xffu8, // points 14 bytes backwards
215            11u8, 0u8, // string is 11 characters long
216        ]);
217
218        #[cfg(all(feature = "pointer_width_16", feature = "big_endian"))]
219        // Synthetic archive (correct)
220        let synthetic_buf = Align([
221            // "Hello world"
222            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
223            0u8, // padding to 2-alignment
224            1u8, 0u8, // Some + padding
225            0xffu8, 0xf2u8, // points 14 bytes backwards
226            0u8, 11u8, // string is 11 characters long
227        ]);
228
229        #[cfg(all(
230            not(any(
231                feature = "pointer_width_16",
232                feature = "pointer_width_64",
233            )),
234            not(feature = "big_endian"),
235        ))]
236        // Synthetic archive (correct)
237        let synthetic_buf = Align([
238            // "Hello world"
239            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
240            0u8, // padding to 4-alignment
241            1u8, 0u8, 0u8, 0u8, // Some + padding
242            0xf0u8, 0xffu8, 0xffu8, 0xffu8, // points 16 bytes backward
243            11u8, 0u8, 0u8, 0u8, // string is 11 characters long
244        ]);
245
246        #[cfg(all(
247            not(any(
248                feature = "pointer_width_16",
249                feature = "pointer_width_64",
250            )),
251            feature = "big_endian",
252        ))]
253        // Synthetic archive (correct)
254        let synthetic_buf = Align([
255            // "Hello world"
256            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
257            0u8, // padding to 4-alignment
258            1u8, 0u8, 0u8, 0u8, // Some + padding
259            0xffu8, 0xffu8, 0xffu8, 0xf0u8, // points 16 bytes backward
260            0u8, 0u8, 0u8, 11u8, // string is 11 characters long
261        ]);
262
263        #[cfg(all(feature = "pointer_width_64", not(feature = "big_endian")))]
264        // Synthetic archive (correct)
265        let synthetic_buf = Align([
266            // "Hello world"
267            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
268            0u8, 0u8, 0u8, 0u8, 0u8, // padding to 8-alignment
269            1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, // Some + padding
270            // points 24 bytes backward
271            0xe8u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8,
272            11u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
273            0u8, // string is 11 characters long
274        ]);
275
276        #[cfg(all(feature = "pointer_width_64", feature = "big_endian"))]
277        // Synthetic archive (correct)
278        let synthetic_buf = Align([
279            // "Hello world!!!!!" because otherwise the string will get inlined
280            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
281            0x21, 0x21, 0x21, 0x21, 0x21, 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
282            0u8, // Some + padding
283            // points 24 bytes backward
284            0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xe8u8, 0u8,
285            0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
286            11u8, // string is 11 characters long
287        ]);
288
289        let result =
290            access::<ArchivedOption<ArchivedBox<[u8]>>, Error>(&*synthetic_buf);
291        result.unwrap();
292
293        // Out of bounds
294        let result =
295            access_pos::<Archived<u32>, Error>(&*Align([0, 1, 2, 3, 4]), 8);
296        assert_source!(
297            result.unwrap_err(),
298            super::InvalidSubtreePointer { size: 4, .. },
299            "error source should be out-of-bounds",
300        );
301        // Overrun
302        let result =
303            access_pos::<Archived<u32>, Error>(&*Align([0, 1, 2, 3, 4]), 4);
304        assert_source!(
305            result.unwrap_err(),
306            super::InvalidSubtreePointer { size: 4, .. },
307            "error source should be overrun",
308        );
309        // Unaligned
310        let result =
311            access_pos::<Archived<u32>, Error>(&*Align([0, 1, 2, 3, 4]), 1);
312        assert_source!(
313            result.unwrap_err(),
314            super::UnalignedPointer { align: 4, .. },
315            "error source should be unaligned",
316        );
317    }
318
319    #[cfg(not(any(
320        feature = "pointer_width_16",
321        feature = "pointer_width_64"
322    )))]
323    #[test]
324    fn invalid_tags() {
325        // Invalid archive (invalid tag)
326        let synthetic_buf = Align([
327            // "Hello world"
328            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
329            0u8, // pad to 4-alignment
330            2u8, 0u8, 0u8, 0u8, // invalid tag + padding
331            0xe8, 0xff, 0xff, 0xff, // points 24 bytes backward
332            11u8, 0u8, 0u8, 0u8, // string is 11 characters long
333        ]);
334
335        let result =
336            access::<ArchivedOption<ArchivedBox<[u8]>>, Error>(&*synthetic_buf);
337        assert_source!(
338            result.unwrap_err(),
339            bytecheck::InvalidEnumDiscriminantError::<u8> {
340                enum_name: "ArchivedOption",
341                invalid_discriminant: 2,
342            },
343            "error source should be invalid enum discriminant",
344        );
345    }
346
347    #[cfg(not(any(
348        feature = "pointer_width_16",
349        feature = "pointer_width_64"
350    )))]
351    #[test]
352    fn overlapping_claims() {
353        // Invalid archive (overlapping claims)
354        let synthetic_buf = Align([
355            // "Hello world"
356            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
357            0, // pad to 4-alignment
358            // First string
359            0xf0, 0xff, 0xff, 0xff, // points 16 bytes backward
360            11u8, 0u8, 0u8, 0u8, // string is 11 characters long
361            // Second string
362            0xe8, 0xff, 0xff, 0xff, // points 24 bytes forward
363            11u8, 0u8, 0u8, 0u8, // string is 11 characters long
364        ]);
365
366        let result = access::<[ArchivedBox<[u8]>; 2], Error>(&*synthetic_buf);
367        assert_source!(
368            result.unwrap_err(),
369            super::InvalidSubtreePointer { size: 11, .. },
370            "error source should be invalid subtree pointer",
371        );
372    }
373
374    #[cfg(not(any(
375        feature = "pointer_width_16",
376        feature = "pointer_width_64"
377    )))]
378    #[cfg(feature = "alloc")]
379    #[test]
380    fn cycle_detection() {
381        use crate::{
382            ser::Writer, validation::ArchiveContext, Archive, Serialize,
383        };
384
385        #[allow(dead_code)]
386        #[derive(Archive, Serialize)]
387        #[rkyv(
388            crate,
389            serialize_bounds(__S: Writer),
390            bytecheck(bounds(__C: ArchiveContext)),
391            derive(Debug),
392        )]
393        enum Node {
394            Nil,
395            Cons(#[rkyv(omit_bounds)] crate::alloc::boxed::Box<Node>),
396        }
397
398        // Invalid archive (cyclic claims)
399        let synthetic_buf = Align([
400            // First node
401            1u8, 0u8, 0u8, 0u8, // Cons
402            4u8, 0u8, 0u8, 0u8, // Node is 4 bytes forward
403            // Second string
404            1u8, 0u8, 0u8, 0u8, // Cons
405            244u8, 255u8, 255u8, 255u8, // Node is 12 bytes back
406        ]);
407
408        let result = access::<ArchivedNode, Error>(&*synthetic_buf);
409        assert_source!(
410            result.unwrap_err(),
411            super::InvalidSubtreePointer { .. },
412            "error source should be invalid subtree pointer",
413        );
414    }
415}