Skip to main content

structured_asn1/
limits.rs

1//! What the caller is willing to spend on one parse.
2//!
3//! These bound work rather than document size: a fixed ceiling refuses real
4//! documents while a hostile input stays under it. Each is checked before the
5//! work it guards.
6
7use core::cell::Cell;
8
9use crate::error::Limit;
10use crate::memory::Memory;
11
12/// How much a parse may spend.
13///
14/// The defaults are generous, because they exist to stop a document that is
15/// trying to make this run forever, not to decide that real documents are too
16/// big. A caller on a device with less to spend lowers them.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Limits {
19    /// How deeply elements may nest.
20    ///
21    /// Certificates and signed data nest perhaps a dozen deep. A thousand is a
22    /// document doing something else.
23    pub max_depth: u32,
24
25    /// How many elements may be read in total.
26    pub max_elements: u64,
27
28    /// How long a single element's contents may be.
29    pub max_element_length: u64,
30
31    /// Cumulative parser and explicitly attached schema work units.
32    ///
33    /// Header scanning, nested-reader paths and diagnostic copying share this
34    /// ceiling. Borrowed payload views do not scan their contents. Explicit
35    /// [`crate::Reader::charge`] and [`crate::Reader::execution`] additionally
36    /// attach schema scans and scratch to the same cumulative session; rebinding
37    /// the aggregate account does not reset it.
38    pub max_work: u64,
39}
40
41impl Default for Limits {
42    fn default() -> Self {
43        Self {
44            max_depth: 1_000,
45            max_elements: 1_000_000,
46            max_element_length: 1 << 32,
47            max_work: 16_000_000,
48        }
49    }
50}
51
52impl Limits {
53    /// Limits that allow nothing beyond a single shallow element, for tests
54    /// and for callers that want to prove a budget is enforced.
55    #[must_use]
56    pub fn none() -> Self {
57        Self {
58            max_depth: 0,
59            max_elements: 0,
60            max_element_length: 0,
61            max_work: 0,
62        }
63    }
64}
65
66/// What has been spent so far, against what was allowed.
67///
68/// The counters are cells so that a reader entered from another reader shares
69/// one budget rather than each getting a fresh one. A per-container budget
70/// would bound nothing: a document could ask for any amount of work by asking
71/// for a little in each of many containers.
72#[derive(Debug)]
73pub(crate) struct Budget {
74    limits: Limits,
75    elements: Cell<u64>,
76    work: Cell<u64>,
77    memory: Memory,
78}
79
80impl Budget {
81    pub(crate) fn new(limits: Limits, memory: Memory) -> Self {
82        Self {
83            limits,
84            elements: Cell::new(0),
85            work: Cell::new(0),
86            memory,
87        }
88    }
89
90    pub(crate) fn limits(&self) -> Limits {
91        self.limits
92    }
93
94    pub(crate) fn memory(&self) -> &Memory {
95        &self.memory
96    }
97
98    /// Charges for one element about to be read, before reading it.
99    ///
100    /// Reports which budget ran out and nothing else: where it happened is
101    /// known to the caller, who assembles that only when there is an error.
102    pub(crate) fn element(&self) -> Result<(), Limit> {
103        let spent = self.elements.get();
104        debug_assert!(spent <= self.limits.max_elements);
105        if spent >= self.limits.max_elements {
106            return Err(Limit::Elements);
107        }
108        self.elements.set(spent + 1);
109        Ok(())
110    }
111
112    /// Charges structure in the original domain for budget unit tests.
113    #[cfg(test)]
114    pub(crate) fn work(&self, amount: u64) -> Result<(), Limit> {
115        self.work_in(amount, &self.memory)
116    }
117
118    /// Retains the session ceiling while charging the current execution domain.
119    pub(crate) fn work_in(&self, amount: u64, execution: &Memory) -> Result<(), Limit> {
120        let spent = self.work.get();
121        debug_assert!(spent <= self.limits.max_work);
122        if amount > self.limits.max_work - spent {
123            return Err(Limit::Work);
124        }
125        execution.charge_work(amount).map_err(|_| Limit::Work)?;
126        self.work.set(spent + amount);
127        Ok(())
128    }
129
130    /// Reserves the session share of an operation whose owned storage methods
131    /// already charge the aggregate account. Never charge that account twice.
132    pub(crate) fn local_work(&self, amount: u64) -> Result<(), Limit> {
133        let spent = self.work.get();
134        debug_assert!(spent <= self.limits.max_work);
135        if amount > self.limits.max_work - spent {
136            return Err(Limit::Work);
137        }
138        self.work.set(spent + amount);
139        Ok(())
140    }
141
142    /// Checks a depth before descending to it.
143    pub(crate) fn depth(&self, depth: u32) -> Result<(), Limit> {
144        if depth > self.limits.max_depth {
145            return Err(Limit::Depth);
146        }
147        Ok(())
148    }
149
150    /// Checks a stated length before trusting it enough to slice with.
151    pub(crate) fn length(&self, length: u64) -> Result<(), Limit> {
152        if length > self.limits.max_element_length {
153            return Err(Limit::ElementLength);
154        }
155        Ok(())
156    }
157}
158
159#[cfg(test)]
160mod tests;