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
80/// One source ceiling and the current operation ceiling, with one account bill.
81/// Foreign-source transitions replace the source, never the operation owner.
82#[derive(Debug, Clone, Copy)]
83pub(crate) struct BudgetView<'a> {
84    origin: &'a Budget,
85    operation: Option<&'a Budget>,
86}
87
88impl<'a> BudgetView<'a> {
89    pub(crate) const fn root(origin: &'a Budget) -> Self {
90        Self {
91            origin,
92            operation: None,
93        }
94    }
95    pub(crate) fn query(origin: &'a Budget, operation: &'a Budget) -> Self {
96        Self {
97            origin,
98            operation: if core::ptr::eq(origin, operation) {
99                None
100            } else {
101                Some(operation)
102            },
103        }
104    }
105    pub(crate) fn operation(self) -> &'a Budget {
106        self.operation.unwrap_or(self.origin)
107    }
108    pub(crate) fn work_in(self, amount: u64, execution: &Memory) -> Result<(), Limit> {
109        if amount > self.origin.limits.max_work - self.origin.work.get() {
110            return Err(Limit::Work);
111        }
112        if let Some(operation) = self.operation
113            && amount > operation.limits.max_work - operation.work.get()
114        {
115            return Err(Limit::Work);
116        }
117        execution.charge_work(amount).map_err(|_| Limit::Work)?;
118        self.origin.work.set(self.origin.work.get() + amount);
119        if let Some(operation) = self.operation {
120            operation.work.set(operation.work.get() + amount);
121        }
122        Ok(())
123    }
124    pub(crate) fn local_work(self, amount: u64) -> Result<(), Limit> {
125        if amount > self.origin.limits.max_work - self.origin.work.get() {
126            return Err(Limit::Work);
127        }
128        if let Some(operation) = self.operation
129            && amount > operation.limits.max_work - operation.work.get()
130        {
131            return Err(Limit::Work);
132        }
133        self.origin.work.set(self.origin.work.get() + amount);
134        if let Some(operation) = self.operation {
135            operation.work.set(operation.work.get() + amount);
136        }
137        Ok(())
138    }
139    pub(crate) fn element(self) -> Result<(), Limit> {
140        if self.origin.elements.get() >= self.origin.limits.max_elements {
141            return Err(Limit::Elements);
142        }
143        if let Some(operation) = self.operation
144            && operation.elements.get() >= operation.limits.max_elements
145        {
146            return Err(Limit::Elements);
147        }
148        self.origin.elements.set(self.origin.elements.get() + 1);
149        if let Some(operation) = self.operation {
150            operation.elements.set(operation.elements.get() + 1);
151        }
152        Ok(())
153    }
154    pub(crate) fn depth(self, depth: u32) -> Result<(), Limit> {
155        self.origin.depth(depth)?;
156        if let Some(operation) = self.operation {
157            operation.depth(depth)?;
158        }
159        Ok(())
160    }
161    pub(crate) fn length(self, length: u64) -> Result<(), Limit> {
162        self.origin.length(length)?;
163        if let Some(operation) = self.operation {
164            operation.length(length)?;
165        }
166        Ok(())
167    }
168}
169
170impl Budget {
171    pub(crate) fn new(limits: Limits, memory: Memory) -> Self {
172        Self {
173            limits,
174            elements: Cell::new(0),
175            work: Cell::new(0),
176            memory,
177        }
178    }
179
180    pub(crate) fn limits(&self) -> Limits {
181        self.limits
182    }
183
184    pub(crate) fn memory(&self) -> &Memory {
185        &self.memory
186    }
187
188    /// Charges for one element about to be read, before reading it.
189    ///
190    /// Reports which budget ran out and nothing else: where it happened is
191    /// known to the caller, who assembles that only when there is an error.
192    #[cfg(test)]
193    pub(crate) fn element(&self) -> Result<(), Limit> {
194        BudgetView::root(self).element()
195    }
196
197    /// Charges structure in the original domain for budget unit tests.
198    #[cfg(test)]
199    pub(crate) fn work(&self, amount: u64) -> Result<(), Limit> {
200        BudgetView::root(self).work_in(amount, &self.memory)
201    }
202
203    /// Checks a depth before descending to it.
204    pub(crate) fn depth(&self, depth: u32) -> Result<(), Limit> {
205        if depth > self.limits.max_depth {
206            return Err(Limit::Depth);
207        }
208        Ok(())
209    }
210
211    /// Checks a stated length before trusting it enough to slice with.
212    pub(crate) fn length(&self, length: u64) -> Result<(), Limit> {
213        if length > self.limits.max_element_length {
214            return Err(Limit::ElementLength);
215        }
216        Ok(())
217    }
218}
219
220#[cfg(test)]
221mod tests;