soroban_env_host/budget/limits.rs
1use crate::{
2 budget::{Budget, BudgetImpl},
3 host::error::TryBorrowOrErr,
4 xdr::{Limits, ScErrorCode, ScErrorType},
5 Error, HostError,
6};
7
8/// These constants are used to set limits on recursion and data length in the
9/// context of XDR (de)serialization. They serve as safeguards against both
10/// excessive stack allocation, which could cause an unrecoverable `SIGABRT`,
11/// and excessive heap memory allocation.
12pub const DEFAULT_XDR_RW_LIMITS: Limits = Limits {
13 // recursion limit for reading and writing XDR structures.
14 depth: 500,
15 // Maximum byte length for a data structure during serialization and
16 // deserialization to and from the XDR format.
17 // **DO NOT** use the default length for de-serialization. Instead,
18 // use the size of the input buffer (which should be much less than
19 // the limit defined here).
20 // The default 32 MB limit for serialization is the last-resort
21 // sanity check. Serialization can't be easily tampered with (unlike
22 // de-serialization) and is guarded by the budget both when the user
23 // creates the objects to be serialized and when we allocate memory
24 // to write these objects into.
25 // We want to be pretty non-restrictive and let budget do its work for
26 // deserialization. While 32 MB is much more data than we would ever
27 // like to materialize, it is possible that the user would
28 // just e.g. hash it without materializing, which seems like a much
29 // more plausible scenario where this can be reached given high
30 // enough memory/CPU instruction limits.
31 len: 32 * 1024 * 1024,
32};
33
34/// - `DEFAULT_HOST_DEPTH_LIMIT`: This limit applies to the host environment. It
35/// guards recursion paths involving the `Env` and `Budget`, particularly
36/// during operations like conversion, comparison, and deep cloning. The limit
37/// is strategically checked at critical recursion points, such as when
38/// encountering a `Val`. As the actual stack usage can be higher,
39/// `DEFAULT_HOST_DEPTH_LIMIT` is conservatively set to a lower value than the
40/// XDR limit.
41pub const DEFAULT_HOST_DEPTH_LIMIT: u32 = 100;
42
43// These are some sane values, however the embedder should typically customize
44// these to match the network config.
45pub(crate) const DEFAULT_CPU_INSN_LIMIT: u64 = 100_000_000;
46pub(crate) const DEFAULT_MEM_BYTES_LIMIT: u64 = 40 * 1024 * 1024; // 40MB
47
48/// `DepthLimiter` is a trait designed for managing the depth of recursive operations.
49/// It provides a mechanism to limit recursion depth, and defines the behavior upon
50/// entering and leaving a recursion level.
51pub(crate) trait DepthLimiter {
52 /// Defines the behavior for entering a new recursion level.
53 /// An `ExceededLimit` is returned if the new level exceeds the depth limit.
54 fn enter(&mut self) -> Result<(), HostError>;
55
56 /// Defines the behavior for leaving a recursion level.
57 fn leave(&mut self) -> Result<(), HostError>;
58
59 /// Wraps a given function `f` with depth limiting guards.
60 /// It triggers an `enter` before, and a `leave` after the execution of `f`.
61 ///
62 /// # Parameters
63 ///
64 /// - `f`: The function to be executed under depth limit constraints.
65 ///
66 /// # Returns
67 ///
68 /// - `Err` if 1. the depth limit has been exceeded upon `enter` 2. `f` executes
69 /// with an error 3. if error occurs on `leave`.
70 /// `Ok` otherwise.
71 fn with_limited_depth<T, F>(&mut self, f: F) -> Result<T, HostError>
72 where
73 F: FnOnce(&mut Self) -> Result<T, HostError>,
74 {
75 self.enter()?;
76 let res = f(self);
77 self.leave()?;
78 res
79 }
80}
81
82impl DepthLimiter for BudgetImpl {
83 fn enter(&mut self) -> Result<(), HostError> {
84 if let Some(depth) = self.depth_limit.checked_sub(1) {
85 self.depth_limit = depth;
86 } else {
87 return Err(Error::from_type_and_code(
88 ScErrorType::Context,
89 ScErrorCode::ExceededLimit,
90 )
91 .into());
92 }
93 Ok(())
94 }
95
96 // `leave` should be called in tandem with `enter` such that the depth
97 // doesn't exceed the initial depth limit.
98 fn leave(&mut self) -> Result<(), HostError> {
99 self.depth_limit = self.depth_limit.checked_add(1).ok_or_else(|| {
100 Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError)
101 })?;
102 Ok(())
103 }
104}
105
106impl DepthLimiter for Budget {
107 fn enter(&mut self) -> Result<(), HostError> {
108 self.0.try_borrow_mut_or_err()?.enter()
109 }
110
111 fn leave(&mut self) -> Result<(), HostError> {
112 self.0.try_borrow_mut_or_err()?.leave()
113 }
114}