Limiting Stack Usage
Most O/S differentiates between heap and stack memory.
Usually the stack (around 1MB) is much smaller than the heap (multiple MB’s or even GB’s).
Therefore, it is possible for a carefully-crafted script to consume all available stack memory (such as deeply-nested expressions) and crash the host system, even though there is ample heap memory available.
Calculate Stack Usage
Some O/S’s provide system calls to get the stack size and/or amount of free stack memory, but these are in the minority.
In order to determine the amount of stack memory actually used, it is necessary to perform some pointer arithmetic.
The trick is to get the address of a stack-allocated variable in the very beginning and compare it to the address of another variable.
// Create a variable on the stack.
let stack_base_ref = Dynamic::UNIT;
// Get a pointer to it.
let stack_base: *const Dynamic = &stack_base_ref;
// ... do a lot of work here ...
// Create another variable on the stack.
let stack_top = Dynamic::UNIT;
// Get a pointer to it.
let stack_top: *const Dynamic = &stack_top;
let usage = unsafe { stack_top.offset_from(stack_base) };
In many cases, the amount of stack memory used is actually negative (meaning that the base variable is in a higher memory address than the current variable).
That is because, for many architectures, the stack grows downwards and the heap grows upwards in order to maximize memory usage efficiency.
During Evaluation
To prevent stack-overflow failures, provide a closure to Engine::on_progress
to
track stack usage and force-terminate a malicious script before it can bring down the host system.
let mut engine = Engine::new();
const MAX_STACK: usize = 100 * 1024; // 10KB
// Create a variable on the stack.
let stack_base_ref = Dynamic::UNIT;
// Get a pointer to it.
let stack_base: *const Dynamic = &stack_base_ref;
engine.on_progress(move |_| {
// Create another variable on the stack.
let stack_top = Dynamic::UNIT;
// Get a pointer to it.
let stack_top: *const Dynamic = &stack_top;
let usage = unsafe { stack_base.offset_from(stack_top) };
if usage > MAX_STACK {
// Terminate the script
Some(Dynamic::UNIT)
} else {
// Continue
None
}
});
During Parsing
A malicious script can be carefully crafted such that it consumes all stack memory during the parsing stage.
Protect against this by via a closure to Engine::on_parse_token
.
let mut engine = Engine::new();
const MAX_STACK: usize = 100 * 1024; // 10KB
// Create a variable on the stack.
let stack_base_ref = Dynamic::UNIT;
// Get a pointer to it.
let stack_base: *const Dynamic = &stack_base_ref;
engine.on_parse_token(|token, _, _| {
// Create another variable on the stack.
let stack_top = Dynamic::UNIT;
// Get a pointer to it.
let stack_top: *const Dynamic = &stack_top;
let usage = unsafe { stack_base.offset_from(stack_top) };
if usage > MAX_STACK {
// Terminate parsing
Token::LexError(
LexError::Runtime("stack-overflow".into()).into()
)
} else {
// Continue
token
}
});