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