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}