Skip to main content

seam_core/
limits.rs

1//! Bounds on hostile input, enforced in the core so every binding inherits
2//! them instead of each deriving its own defaults.
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Limits {
6    /// The validator recurses, so this is what protects the stack.
7    pub max_depth: usize,
8    pub max_items: usize,
9    pub max_string_bytes: usize,
10    pub max_object_keys: usize,
11}
12
13impl Limits {
14    /// The highest `max_depth` the engine will honour, whatever it is handed.
15    ///
16    /// Both the JSON parser and the validator recurse, so depth is what stands
17    /// between a hostile document and the stack. A stack overflow is not a
18    /// panic that a binding can catch and turn into an error: it kills the
19    /// process, taking every other request in flight with it. Measured, that
20    /// starts happening somewhere between one and five thousand levels on the
21    /// object path, so a caller raising `max_depth` freely could disable the
22    /// one bound that is not recoverable.
23    ///
24    /// 256 is the value `PERMISSIVE` already used for trusted input, and it is
25    /// far past any document a person meant to send. A limit that can be
26    /// turned off until the process dies is not a limit.
27    pub const MAX_DEPTH: usize = 256;
28
29    pub const DEFAULT: Self = Self {
30        max_depth: 64,
31        max_items: 10_000,
32        max_string_bytes: 1 << 20,
33        max_object_keys: 1_000,
34    };
35
36    /// For trusted input. Depth stays bounded because the stack is.
37    pub const PERMISSIVE: Self = Self {
38        max_depth: 256,
39        max_items: usize::MAX,
40        max_string_bytes: usize::MAX,
41        max_object_keys: usize::MAX,
42    };
43}
44
45impl Limits {
46    /// The limits actually used, with `max_depth` held under [`Self::MAX_DEPTH`].
47    ///
48    /// Applied by the engine at both entry points rather than by each binding,
49    /// so no binding can hand the recursion a number the stack cannot take.
50    #[must_use]
51    pub const fn clamped(self) -> Self {
52        Self {
53            max_depth: if self.max_depth > Self::MAX_DEPTH {
54                Self::MAX_DEPTH
55            } else {
56                self.max_depth
57            },
58            ..self
59        }
60    }
61}
62
63// Checked when the crate compiles, not when a test runs: a preset above the
64// cap would be a contradiction in the engine's own configuration, and there is
65// no reason to let it build.
66const _: () = assert!(Limits::DEFAULT.max_depth <= Limits::MAX_DEPTH);
67const _: () = assert!(Limits::PERMISSIVE.max_depth <= Limits::MAX_DEPTH);
68
69impl Default for Limits {
70    fn default() -> Self {
71        Self::DEFAULT
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn max_depth_is_capped_however_it_arrives() {
81        let reckless = Limits { max_depth: 1_000_000, ..Limits::DEFAULT };
82        assert_eq!(reckless.clamped().max_depth, Limits::MAX_DEPTH);
83
84        // Everything else is the caller's business: those bound memory, and
85        // exceeding them is reported rather than fatal.
86        assert_eq!(reckless.clamped().max_items, Limits::DEFAULT.max_items);
87
88        // A limit below the cap is left alone.
89        let tight = Limits { max_depth: 4, ..Limits::DEFAULT };
90        assert_eq!(tight.clamped().max_depth, 4);
91    }
92
93    #[test]
94    fn the_default_is_the_conservative_one() {
95        assert_eq!(Limits::default(), Limits::DEFAULT);
96        assert_eq!(
97            Limits::DEFAULT.max_items.min(Limits::PERMISSIVE.max_items),
98            Limits::DEFAULT.max_items
99        );
100    }
101}