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;