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    /// Admit a joint operation without replacing either existing source and
125    /// without billing the account or an aliased parser twice.
126    pub(crate) fn work_with(
127        self,
128        sources: [&Budget; 2],
129        amount: u64,
130        execution: &Memory,
131    ) -> Result<(), Limit> {
132        let mut extras = [None; 2];
133        for (i, extra) in sources.into_iter().enumerate() {
134            if core::ptr::eq(extra, self.origin) {
135                continue;
136            }
137            if let Some(operation) = self.operation
138                && core::ptr::eq(extra, operation)
139            {
140                continue;
141            }
142            if i == 1 && core::ptr::eq(extra, sources[0]) {
143                continue;
144            }
145            if amount > extra.limits.max_work - extra.work.get() {
146                return Err(Limit::Work);
147            }
148            extras[i] = Some(extra);
149        }
150        self.work_in(amount, execution)?;
151        for extra in extras.into_iter().flatten() {
152            extra.work.set(extra.work.get() + amount);
153        }
154        Ok(())
155    }
156    /// The largest amount both local ceilings of [`Self::work_in`] admit now.
157    pub(crate) fn work_remaining(self) -> u64 {
158        let origin = self.origin.limits.max_work - self.origin.work.get();
159        match self.operation {
160            Some(operation) => origin.min(operation.limits.max_work - operation.work.get()),
161            None => origin,
162        }
163    }
164    pub(crate) fn local_work(self, amount: u64) -> Result<(), Limit> {
165        if amount > self.origin.limits.max_work - self.origin.work.get() {
166            return Err(Limit::Work);
167        }
168        if let Some(operation) = self.operation
169            && amount > operation.limits.max_work - operation.work.get()
170        {
171            return Err(Limit::Work);
172        }
173        self.origin.work.set(self.origin.work.get() + amount);
174        if let Some(operation) = self.operation {
175            operation.work.set(operation.work.get() + amount);
176        }
177        Ok(())
178    }
179    pub(crate) fn element(self) -> Result<(), Limit> {
180        if self.origin.elements.get() >= self.origin.limits.max_elements {
181            return Err(Limit::Elements);
182        }
183        if let Some(operation) = self.operation
184            && operation.elements.get() >= operation.limits.max_elements
185        {
186            return Err(Limit::Elements);
187        }
188        self.origin.elements.set(self.origin.elements.get() + 1);
189        if let Some(operation) = self.operation {
190            operation.elements.set(operation.elements.get() + 1);
191        }
192        Ok(())
193    }
194    pub(crate) fn depth(self, depth: u32) -> Result<(), Limit> {
195        self.origin.depth(depth)?;
196        if let Some(operation) = self.operation {
197            operation.depth(depth)?;
198        }
199        Ok(())
200    }
201    pub(crate) fn length(self, length: u64) -> Result<(), Limit> {
202        self.origin.length(length)?;
203        if let Some(operation) = self.operation {
204            operation.length(length)?;
205        }
206        Ok(())
207    }
208}
209
210impl Budget {
211    pub(crate) fn new(limits: Limits, memory: Memory) -> Self {
212        Self {
213            limits,
214            elements: Cell::new(0),
215            work: Cell::new(0),
216            memory,
217        }
218    }
219
220    pub(crate) fn limits(&self) -> Limits {
221        self.limits
222    }
223
224    pub(crate) fn memory(&self) -> &Memory {
225        &self.memory
226    }
227
228    /// Charges for one element about to be read, before reading it.
229    ///
230    /// Reports which budget ran out and nothing else: where it happened is
231    /// known to the caller, who assembles that only when there is an error.
232    #[cfg(test)]
233    pub(crate) fn element(&self) -> Result<(), Limit> {
234        BudgetView::root(self).element()
235    }
236
237    /// Charges structure in the original domain for budget unit tests.
238    #[cfg(test)]
239    pub(crate) fn work(&self, amount: u64) -> Result<(), Limit> {
240        BudgetView::root(self).work_in(amount, &self.memory)
241    }
242
243    /// Checks a depth before descending to it.
244    pub(crate) fn depth(&self, depth: u32) -> Result<(), Limit> {
245        if depth > self.limits.max_depth {
246            return Err(Limit::Depth);
247        }
248        Ok(())
249    }
250
251    /// Checks a stated length before trusting it enough to slice with.
252    pub(crate) fn length(&self, length: u64) -> Result<(), Limit> {
253        if length > self.limits.max_element_length {
254            return Err(Limit::ElementLength);
255        }
256        Ok(())
257    }
258}
259
260#[cfg(test)]
261mod tests;