reorganize module
This commit is contained in:
		
							
								
								
									
										98
									
								
								_archive/rhai_engine/rhaibook/safety/checked.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								_archive/rhai_engine/rhaibook/safety/checked.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
Turning Off Safety Checks
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Checked Arithmetic
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
```admonish bug.side "Don't Panic"
 | 
			
		||||
 | 
			
		||||
Scripts under normal builds of Rhai never crash the host system – any panic is a bug.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates
 | 
			
		||||
with a runtime error whenever it detects a numeric over-flow/under-flow condition or an invalid
 | 
			
		||||
floating-point operation.
 | 
			
		||||
 | 
			
		||||
This checking can be turned off via the [`unchecked`] feature for higher performance (but higher
 | 
			
		||||
risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let x = 1_000_000_000_000;
 | 
			
		||||
 | 
			
		||||
x * x;      // Normal build - runtime error: multiplication overflow
 | 
			
		||||
 | 
			
		||||
x * x;      // 'unchecked' debug build - panic!
 | 
			
		||||
            // 'unchecked' release build - overflow with no error
 | 
			
		||||
 | 
			
		||||
x / 0;      // Normal build - runtime error: division by zero
 | 
			
		||||
 | 
			
		||||
x / 0;      // 'unchecked' build - panic!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Other Safety Checks
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
In addition to overflows, there are many other safety checks performed by Rhai at runtime.
 | 
			
		||||
[`unchecked`] turns them **all** off as well, such as...
 | 
			
		||||
 | 
			
		||||
### [Infinite loops][maximum number of operations]
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Normal build - runtime error: exceeds maximum number of operations
 | 
			
		||||
loop {
 | 
			
		||||
    foo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 'unchecked' build - never terminates!
 | 
			
		||||
loop {
 | 
			
		||||
    foo();
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### [Infinite recursion][maximum call stack depth]
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
fn foo() {
 | 
			
		||||
    foo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
foo();      // Normal build - runtime error: exceeds maximum stack depth
 | 
			
		||||
 | 
			
		||||
foo();      // 'unchecked' build - panic due to stack overflow!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### [Gigantic data structures][maximum size of arrays]
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let x = [];
 | 
			
		||||
 | 
			
		||||
// Normal build - runtime error: array exceeds maximum size
 | 
			
		||||
loop {
 | 
			
		||||
    x += 42;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 'unchecked' build - panic due to out-of-memory!
 | 
			
		||||
loop {
 | 
			
		||||
    x += 42;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Improper range iteration
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Normal build - runtime error: zero step
 | 
			
		||||
for x in range(0, 10, 0) { ... }
 | 
			
		||||
 | 
			
		||||
// 'unchecked' build - never terminates!
 | 
			
		||||
for x in range(0, 10, 0) { ... }
 | 
			
		||||
 | 
			
		||||
// Normal build - empty range
 | 
			
		||||
for x in range(0, 10, -1) { ... }
 | 
			
		||||
 | 
			
		||||
// 'unchecked' build - panic due to numeric underflow!
 | 
			
		||||
for x in range(0, 10, -1) { ... }
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										115
									
								
								_archive/rhai_engine/rhaibook/safety/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								_archive/rhai_engine/rhaibook/safety/index.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
Safety and Protection Against DOS Attacks
 | 
			
		||||
=========================================
 | 
			
		||||
 | 
			
		||||
{{#title Safety and Protection}}
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
For scripting systems open to untrusted user-land scripts, it is always best to limit the amount of
 | 
			
		||||
resources used by a script so that it does not consume more resources that it is allowed to.
 | 
			
		||||
 | 
			
		||||
These are common vectors for [Denial of Service (DOS)][DOS] attacks.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Most Important Resources
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
```admonish bug "Memory"
 | 
			
		||||
* Continuously grow a [string], an [array], a [BLOB] or [object map] until all memory is consumed.
 | 
			
		||||
 | 
			
		||||
* Continuously create new [variables] with large data until all memory is consumed.
 | 
			
		||||
 | 
			
		||||
* Continuously define new [functions] all memory is consumed (e.g. a simple [closure] `||`,
 | 
			
		||||
  as short as two characters, is a [function] – an attractive target for DOS attacks).
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "CPU"
 | 
			
		||||
 | 
			
		||||
* Infinite tight loop that consumes all CPU cycles.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "Time"
 | 
			
		||||
 | 
			
		||||
* Run indefinitely, thereby blocking the calling system which is waiting for a result.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "Stack"
 | 
			
		||||
 | 
			
		||||
* Deep recursive call that exhausts the call stack.
 | 
			
		||||
 | 
			
		||||
* Large [array] or [object map] literal that exhausts the stack during parsing.
 | 
			
		||||
 | 
			
		||||
* Degenerated deep expression with so many levels that the parser exhausts the call stack when
 | 
			
		||||
  parsing the expression; or even deeply-nested statements blocks, if nested deep enough.
 | 
			
		||||
 | 
			
		||||
* [Self-referencing module][`import`].
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "Overflows or Underflows"
 | 
			
		||||
 | 
			
		||||
* Numeric overflows and/or underflows.
 | 
			
		||||
 | 
			
		||||
* Divide by zero.
 | 
			
		||||
 | 
			
		||||
* Bad floating-point representations.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "Files"
 | 
			
		||||
 | 
			
		||||
* Continuously [`import`] an external [module] within an infinite loop, thus putting heavy load on the file-system
 | 
			
		||||
  (or even the network if the file is not local).
 | 
			
		||||
 | 
			
		||||
  Even when [modules] are not created from files, they still typically consume a lot of resources to load.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish bug "Private data"
 | 
			
		||||
* Read from and/or write to private, secret, sensitive data.
 | 
			
		||||
 | 
			
		||||
  Such security breach may put the entire system at risk.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish bug "The [`internals`] feature"
 | 
			
		||||
 | 
			
		||||
The [`internals`] feature allows third-party access to Rust internal data types and functions (for
 | 
			
		||||
example, the [`AST`] and related types).
 | 
			
		||||
 | 
			
		||||
This is usually a _Very Bad Idea™_ because:
 | 
			
		||||
 | 
			
		||||
* Messing up Rhai's internal data structures will easily create panics that bring down the host
 | 
			
		||||
  environment, violating the _Don't Panic_ guarantee.
 | 
			
		||||
 | 
			
		||||
* Allowing access to internal types may open up new attack vectors.
 | 
			
		||||
 | 
			
		||||
* Internal Rhai types and functions are volatile, so they may change from version to version and
 | 
			
		||||
  break code.
 | 
			
		||||
 | 
			
		||||
Use [`internals`] only if the operating environment has absolutely no safety concerns – you'd
 | 
			
		||||
be surprised under how few scenarios this assumption holds.
 | 
			
		||||
 | 
			
		||||
One example of such an environment is a Rhai scripting [`Engine`] compiled to [WASM] where the
 | 
			
		||||
[`AST`] is further translated to include environment-specific modifications.
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_Don't Panic_ Guarantee – Any Panic is a Bug
 | 
			
		||||
--------------------------------------------------
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
Rhai is designed to not bring down the host system, regardless of what a script may do to it. This
 | 
			
		||||
is a central design goal – Rhai provides a [_Don't
 | 
			
		||||
Panic_](https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Don't_Panic)
 | 
			
		||||
guarantee.
 | 
			
		||||
 | 
			
		||||
When using Rhai, any panic outside of APIs with explicitly documented panic conditions is
 | 
			
		||||
considered a bug in Rhai and should be reported as such.
 | 
			
		||||
 | 
			
		||||
```admonish tip.small "OK, panic anyway"
 | 
			
		||||
 | 
			
		||||
All these safe-guards can be turned off via the [`unchecked`] feature, which disables all safety
 | 
			
		||||
checks (even fatal ones).
 | 
			
		||||
 | 
			
		||||
This increases script evaluation performance somewhat, but very easy for a malicious script
 | 
			
		||||
to bring down the host system.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										10
									
								
								_archive/rhai_engine/rhaibook/safety/limits.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								_archive/rhai_engine/rhaibook/safety/limits.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
Built-In Safety Limits
 | 
			
		||||
======================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Rhai has a number of safety limits built into the [`Engine`].
 | 
			
		||||
 | 
			
		||||
All these limits can be disabled, for higher performance (but higher risks as well), via the
 | 
			
		||||
[`unchecked`] feature.
 | 
			
		||||
							
								
								
									
										63
									
								
								_archive/rhai_engine/rhaibook/safety/max-array-size.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								_archive/rhai_engine/rhaibook/safety/max-array-size.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
Maximum Size of Arrays
 | 
			
		||||
======================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how large an [array] or a [BLOB] can be.
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_array_size`][options] method, with zero being
 | 
			
		||||
unlimited (the default).
 | 
			
		||||
 | 
			
		||||
A script attempting to create an [array] literal larger than the maximum will terminate with a parse error.
 | 
			
		||||
 | 
			
		||||
Any script operation that produces an [array] or a [BLOB] larger than the maximum also terminates
 | 
			
		||||
the script with an error result.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_array_size(500);     // allow arrays only up to 500 items
 | 
			
		||||
 | 
			
		||||
engine.set_max_array_size(0);       // allow unlimited arrays
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Maximum size"
 | 
			
		||||
 | 
			
		||||
Be conservative when setting a maximum limit and always consider the fact that a registered function
 | 
			
		||||
may grow an [array]'s or [BLOB]'s size without Rhai noticing until the very end.
 | 
			
		||||
 | 
			
		||||
For instance, the built-in `+` operator for [arrays] and [BLOB's] concatenates two of them together
 | 
			
		||||
to form one larger [array] or [BLOB]; if both sources are _slightly_ below the maximum size limit,
 | 
			
		||||
the result may be almost _twice_ the maximum size.
 | 
			
		||||
 | 
			
		||||
As a malicious script may also create a deeply-nested [array] which consumes huge amounts of memory
 | 
			
		||||
while each individual [array] still stays under the maximum size limit, Rhai also _recursively_ adds
 | 
			
		||||
up the sizes of all [strings], [arrays], [blobs] and [object maps] contained within each [array] to
 | 
			
		||||
make sure that the _aggregate_ sizes of none of these data structures exceed their respective
 | 
			
		||||
maximum size limits (if any).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Small, innocent array...
 | 
			
		||||
let small_array = [42];             // 1-deep... 1 item, 1 array
 | 
			
		||||
 | 
			
		||||
// ... becomes huge when multiplied!
 | 
			
		||||
small_array.push(small_array);      // 2-deep... 2 items, 2 arrays
 | 
			
		||||
small_array.push(small_array);      // 3-deep... 4 items, 4 arrays
 | 
			
		||||
small_array.push(small_array);      // 4-deep... 8 items, 8 arrays
 | 
			
		||||
small_array.push(small_array);      // 5-deep... 16 items, 16 arrays
 | 
			
		||||
          :
 | 
			
		||||
          :
 | 
			
		||||
small_array.push(small_array);      // <- Rhai raises an error somewhere here
 | 
			
		||||
small_array.push(small_array);      //    when the TOTAL number of items in
 | 
			
		||||
small_array.push(small_array);      //    the entire array tree exceeds limit
 | 
			
		||||
 | 
			
		||||
// Or this abomination...
 | 
			
		||||
let a = [ 42 ];
 | 
			
		||||
 | 
			
		||||
loop {
 | 
			
		||||
    a[0] = a;       // <- only 1 item, but infinite number of arrays
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										88
									
								
								_archive/rhai_engine/rhaibook/safety/max-call-stack.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								_archive/rhai_engine/rhaibook/safety/max-call-stack.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
Maximum Call Stack Depth
 | 
			
		||||
========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
In Rhai, it is trivial for a function call to perform _infinite recursion_ (or a very deeply-nested
 | 
			
		||||
recursion) such that all stack space is exhausted.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// This is a function that, when called, recurses forever.
 | 
			
		||||
fn recurse_forever() {
 | 
			
		||||
    recurse_forever();
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish info.side "Main stack size"
 | 
			
		||||
 | 
			
		||||
The main stack-size of a program is _not_ determined by Rust but is platform-dependent.
 | 
			
		||||
 | 
			
		||||
See [this on-line Rust docs](https://doc.rust-lang.org/std/thread/#stack-size) for more details.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Because of its intended embedded usage, Rhai, by default, limits function calls to a maximum depth
 | 
			
		||||
of 64 levels (8 levels in debug build) in order to fit into most platforms' default stack sizes.
 | 
			
		||||
 | 
			
		||||
This limit may be changed via the [`Engine::set_max_call_levels`][options] method.
 | 
			
		||||
 | 
			
		||||
A script exceeding the maximum call stack depth will terminate with an error result.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_call_levels(10);     // allow only up to 10 levels of function calls
 | 
			
		||||
 | 
			
		||||
engine.set_max_call_levels(0);      // allow no function calls at all (max depth = zero)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```admonish info.small "Additional considerations"
 | 
			
		||||
 | 
			
		||||
When setting this limit, care must be also be taken to the evaluation depth of each _statement_
 | 
			
		||||
within a function.
 | 
			
		||||
 | 
			
		||||
It is entirely possible for a malicious script to embed a recursive call deep inside a nested
 | 
			
		||||
expression or statements block (see [maximum statement depth]).
 | 
			
		||||
 | 
			
		||||
~~~rust
 | 
			
		||||
fn bad_function(n) {
 | 
			
		||||
    // Bail out long before reaching the limit
 | 
			
		||||
    if n > 10 {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Nest many, many levels deep...
 | 
			
		||||
    if check_1() {
 | 
			
		||||
        if check_2() {
 | 
			
		||||
            if check_3() {
 | 
			
		||||
                if check_4() {
 | 
			
		||||
                        :
 | 
			
		||||
                    if check_n() {
 | 
			
		||||
                        bad_function(n+1);  // <- recursive call!
 | 
			
		||||
                    }
 | 
			
		||||
                        :
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The function call below may still overflow the stack!
 | 
			
		||||
bad_function(0);
 | 
			
		||||
~~~
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish tip.small "Tip: Getting around the stack size limit"
 | 
			
		||||
 | 
			
		||||
While the stack size of a program's _main_ thread is platform-specific, Rust defaults to a stack
 | 
			
		||||
size of 2MB for spawned threads.
 | 
			
		||||
 | 
			
		||||
This default can further be changed such that a spawned thread has as large a stack as needed.
 | 
			
		||||
 | 
			
		||||
See [the on-line Rust docs](https://doc.rust-lang.org/std/thread/#stack-size) for more details.
 | 
			
		||||
 | 
			
		||||
Therefore, in order to relax the stack size limit for scripts, run the [`Engine`] in a separate
 | 
			
		||||
spawned thread with a larger stack.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										24
									
								
								_archive/rhai_engine/rhaibook/safety/max-functions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								_archive/rhai_engine/rhaibook/safety/max-functions.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
Maximum Number of Functions
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how many [functions] can be defined in a script.
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_functions`][options] method. Notice that setting the
 | 
			
		||||
maximum number of [functions] to zero does _not_ indicate unlimited [functions], but disallows
 | 
			
		||||
defining any scripted [function] altogether.
 | 
			
		||||
 | 
			
		||||
A script attempting to load more than the maximum number of [functions] will terminate with a parse error.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_functions(5);        // allow defining only up to 5 functions
 | 
			
		||||
 | 
			
		||||
engine.set_max_functions(0);        // disallow defining function (maximum = zero)
 | 
			
		||||
 | 
			
		||||
engine.set_max_functions(1000);     // set to a large number for effectively unlimited functions
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										64
									
								
								_archive/rhai_engine/rhaibook/safety/max-map-size.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								_archive/rhai_engine/rhaibook/safety/max-map-size.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
Maximum Size of Object Maps
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
 | 
			
		||||
 | 
			
		||||
This can be changed via `Engine::set_max_map_size`, with zero being unlimited (the default).
 | 
			
		||||
 | 
			
		||||
A script attempting to create an [object map] literal with more properties than the maximum will
 | 
			
		||||
terminate with a parse error.
 | 
			
		||||
 | 
			
		||||
Any script operation that produces an [object map] with more properties than the maximum also
 | 
			
		||||
terminates the script with an error.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_map_size(500);       // allow object maps with only up to 500 properties
 | 
			
		||||
 | 
			
		||||
engine.set_max_map_size(0);         // allow unlimited object maps
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Maximum size"
 | 
			
		||||
 | 
			
		||||
Be conservative when setting a maximum limit and always consider the fact that a registered function
 | 
			
		||||
may grow an [object map]'s size without Rhai noticing until the very end.
 | 
			
		||||
 | 
			
		||||
For instance, the built-in `+` operator for [object maps] concatenates two [object maps] together to
 | 
			
		||||
form one larger [object map]; if both [object maps] are _slightly_ below the maximum size limit, the
 | 
			
		||||
resultant [object map] may be almost _twice_ the maximum size.
 | 
			
		||||
 | 
			
		||||
As a malicious script may create a deeply-nested [object map] which consumes huge amounts of memory
 | 
			
		||||
while each individual [object map] still stays under the maximum size limit, Rhai also _recursively_
 | 
			
		||||
adds up the sizes of all [strings], [arrays] and [object maps] contained within each [object map] to
 | 
			
		||||
make sure that the _aggregate_ sizes of none of these data structures exceed their respective
 | 
			
		||||
maximum size limits (if any).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Small, innocent object map...
 | 
			
		||||
let small_map: #{ x: 42 };          // 1-deep... 1 item, 1 object map
 | 
			
		||||
 | 
			
		||||
// ... becomes huge when multiplied!
 | 
			
		||||
small_map.y = small_map;            // 2-deep... 2 items, 2 object maps
 | 
			
		||||
small_map.y = small_map;            // 3-deep... 4 items, 4 object maps
 | 
			
		||||
small_map.y = small_map;            // 4-deep... 8 items, 8 object maps
 | 
			
		||||
small_map.y = small_map;            // 5-deep... 16 items, 16 object maps
 | 
			
		||||
          :
 | 
			
		||||
          :
 | 
			
		||||
small_map.y = small_map;            // <- Rhai raises an error somewhere here
 | 
			
		||||
small_map.y = small_map;            //    when the TOTAL number of items in
 | 
			
		||||
small_map.y = small_map;            //    the entire array tree exceeds limit
 | 
			
		||||
 | 
			
		||||
// Or this abomination...
 | 
			
		||||
let map = #{ x: 42 };
 | 
			
		||||
 | 
			
		||||
loop {
 | 
			
		||||
    map.x = map;    // <- only 1 item, but infinite number of object maps
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										27
									
								
								_archive/rhai_engine/rhaibook/safety/max-modules.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								_archive/rhai_engine/rhaibook/safety/max-modules.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
Maximum Number of Modules
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how many [modules] can be loaded via [`import`] statements.
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_modules`][options] method. Notice that setting the
 | 
			
		||||
maximum number of modules to zero does _not_ indicate unlimited modules, but disallows loading any
 | 
			
		||||
module altogether.
 | 
			
		||||
 | 
			
		||||
A script attempting to load more than the maximum number of modules will terminate with an error result.
 | 
			
		||||
 | 
			
		||||
This limit can also be used to stop [`import`-loops][`import`] (i.e. cycles of modules referring to
 | 
			
		||||
each other).
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_modules(5);      // allow loading only up to 5 modules
 | 
			
		||||
 | 
			
		||||
engine.set_max_modules(0);      // disallow loading any module (maximum = zero)
 | 
			
		||||
 | 
			
		||||
engine.set_max_modules(1000);   // set to a large number for effectively unlimited modules
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										51
									
								
								_archive/rhai_engine/rhaibook/safety/max-operations.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								_archive/rhai_engine/rhaibook/safety/max-operations.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
Maximum Number of Operations
 | 
			
		||||
============================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
In Rhai, it is trivial to construct _infinite loops_, or scripts that run for a very long time.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
loop { ... }                        // infinite loop
 | 
			
		||||
 | 
			
		||||
while 1 < 2 { ... }                 // loop with always-true condition
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how much time or CPU a script consumes.
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_operations`][options] method, with zero being
 | 
			
		||||
unlimited (the default).
 | 
			
		||||
 | 
			
		||||
The _operations count_ is intended to be a very course-grained measurement of the amount of CPU that
 | 
			
		||||
a script has consumed, allowing the system to impose a hard upper limit on computing resources.
 | 
			
		||||
 | 
			
		||||
A script exceeding the maximum operations count terminates with an error result. This can be
 | 
			
		||||
disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_operations(500);     // allow only up to 500 operations for this script
 | 
			
		||||
 | 
			
		||||
engine.set_max_operations(0);       // allow unlimited operations
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```admonish question "TL;DR – What does one _operation_ mean?"
 | 
			
		||||
 | 
			
		||||
The concept of one single _operation_ in Rhai is volatile – it roughly equals one expression
 | 
			
		||||
node, loading one [variable]/[constant], one [operator] call, one iteration of a loop, or one
 | 
			
		||||
[function] call etc. with sub-expressions, statements and [function] calls executed inside these
 | 
			
		||||
contexts accumulated on top.
 | 
			
		||||
 | 
			
		||||
A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations.
 | 
			
		||||
 | 
			
		||||
One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars.
 | 
			
		||||
For example, loading a [constant] consumes very few CPU cycles, while calling an external Rust function,
 | 
			
		||||
though also counted as only one operation, may consume much more computing resources.
 | 
			
		||||
 | 
			
		||||
To help visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU
 | 
			
		||||
which includes _specialized_ instructions, such as _function call_, _load module_ etc., each taking up
 | 
			
		||||
one CPU cycle to execute.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										71
									
								
								_archive/rhai_engine/rhaibook/safety/max-stmt-depth.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								_archive/rhai_engine/rhaibook/safety/max-stmt-depth.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
Maximum Expression Nesting Depth
 | 
			
		||||
================================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default limits statement and expression nesting to a maximum depth of 64 (which should be
 | 
			
		||||
plenty) when they are at _global_ level, but only a depth of 32 when they are within [function] bodies.
 | 
			
		||||
 | 
			
		||||
For debug builds, these limits are set further downwards to 32 and 16 respectively.
 | 
			
		||||
 | 
			
		||||
That is because it is possible to overflow the [`Engine`]'s stack when it tries to recursively parse
 | 
			
		||||
an extremely deeply-nested code stream.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// The following, if long enough, can easily cause stack overflow during parsing.
 | 
			
		||||
let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(...)+1)))))))))));
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This limit may be changed via [`Engine::set_max_expr_depths`][options].
 | 
			
		||||
 | 
			
		||||
There are two limits to set, one for the maximum depth at global level, and the other for [function] bodies.
 | 
			
		||||
 | 
			
		||||
A script exceeding the maximum nesting depths will terminate with a parse error. The malicious
 | 
			
		||||
[`AST`] will not be able to get past parsing in the first place.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_expr_depths(50, 5);  // allow nesting up to 50 layers of expressions/statements
 | 
			
		||||
                                    // at global level, but only 5 inside functions
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish warning
 | 
			
		||||
Multiple layers of expressions may be generated for a simple language construct, even though it may correspond
 | 
			
		||||
to only one AST node.
 | 
			
		||||
 | 
			
		||||
That is because the Rhai _parser_ internally runs a recursive chain of [function] calls and it is important that
 | 
			
		||||
a malicious script does not panic the parser in the first place.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Beware of recursion"
 | 
			
		||||
 | 
			
		||||
_[Functions]_ are placed under stricter limits because of the multiplicative effect of _recursion_.
 | 
			
		||||
 | 
			
		||||
A [function] can effectively call itself while deep inside an expression chain within the [function]
 | 
			
		||||
body, thereby overflowing the stack even when the level of recursion is within limit.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
fn deep_calc(a, n) {
 | 
			
		||||
    (a+(a+(a+(a+(a+(a+(a+(a+(a+ ... (a+deep_calc(a,n+1)) ... )))))))))
 | 
			
		||||
    //                                 ^^^^^^^^^^^^^^^^ recursive call!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let a = 42;
 | 
			
		||||
 | 
			
		||||
let result = (a+(a+(a+(a+(a+(a+(a+(a+(a+ ... (a+deep_calc(a,0)) ... )))))))));
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In the contrived example above, each recursive call to the [function] `deep_calc` adds the total
 | 
			
		||||
number of nested expression layers to Rhai's evaluation stack.  Sooner or later (most likely sooner
 | 
			
		||||
than the limit for [maximum depth of function calls][maximum call stack depth] is reached), a stack
 | 
			
		||||
overflow can be expected.
 | 
			
		||||
 | 
			
		||||
In general, make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow, where:
 | 
			
		||||
 | 
			
		||||
* `C` = maximum call stack depth,
 | 
			
		||||
* `F` = maximum statement depth for [functions],
 | 
			
		||||
* `S` = maximum statement depth at global level.
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										34
									
								
								_archive/rhai_engine/rhaibook/safety/max-string-size.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								_archive/rhai_engine/rhaibook/safety/max-string-size.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
Maximum Length of Strings
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how long a [string] can be.
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_string_size`][options] method, with zero being
 | 
			
		||||
unlimited (the default).
 | 
			
		||||
 | 
			
		||||
A script attempting to create a [string] literal longer than the maximum length will terminate with
 | 
			
		||||
a parse error.
 | 
			
		||||
 | 
			
		||||
Any script operation that produces a [string] longer than the maximum also terminates the script
 | 
			
		||||
with an error.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_string_size(500);    // allow strings only up to 500 bytes long (in UTF-8 format)
 | 
			
		||||
 | 
			
		||||
engine.set_max_string_size(0);      // allow unlimited string length
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish danger.small "Maximum length"
 | 
			
		||||
 | 
			
		||||
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
 | 
			
		||||
a string's length without Rhai noticing until the very end.
 | 
			
		||||
 | 
			
		||||
For instance, the built-in `+` operator for strings concatenates two strings together to form one longer string;
 | 
			
		||||
if both strings are _slightly_ below the maximum length limit, the resultant string may be almost _twice_ the maximum length.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										54
									
								
								_archive/rhai_engine/rhaibook/safety/max-variables.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								_archive/rhai_engine/rhaibook/safety/max-variables.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
Maximum Number of Variables
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai by default does not limit how many [variables]/[constants] can be defined within a single [`Scope`].
 | 
			
		||||
 | 
			
		||||
This can be changed via the [`Engine::set_max_variables`][options] method. Notice that setting the
 | 
			
		||||
maximum number of [variables] to zero does _not_ indicate unlimited [variables], but disallows
 | 
			
		||||
defining any [variable] altogether.
 | 
			
		||||
 | 
			
		||||
A script attempting to define more than the maximum number of [variables]/[constants] will terminate
 | 
			
		||||
with an error result.
 | 
			
		||||
 | 
			
		||||
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.set_max_variables(5);      // allow defining only up to 5 variables
 | 
			
		||||
 | 
			
		||||
engine.set_max_variables(0);      // disallow defining any variable (maximum = zero)
 | 
			
		||||
 | 
			
		||||
engine.set_max_variables(1000);   // set to a large number for effectively unlimited variables
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish warning.small "Function calls are separate scopes"
 | 
			
		||||
 | 
			
		||||
Each [function] call creates a new, empty [`Scope`].
 | 
			
		||||
 | 
			
		||||
Therefore, [variables]/[constants] defined within [functions] are counted afresh.
 | 
			
		||||
 | 
			
		||||
Care must be taken to avoid deeply-nested (or recursive) [function] calls from creating too many
 | 
			
		||||
[variables]/[constants] while staying within the limit of each individual [`Scope`].
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish warning.small "Function call arguments count as variables"
 | 
			
		||||
 | 
			
		||||
The parameters of a [function] also count as [variables] within the [function]'s [`Scope`].
 | 
			
		||||
 | 
			
		||||
Thus the maximum number of [variables]/[constants] allowed is reduced by the number of parameters of the [function].
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish tip.small "Tip: Reusing a variable doesn't count"
 | 
			
		||||
 | 
			
		||||
It is possible to _reuse_ a [variable] such that it is counted only once.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let x = 42;     // counted as 1 variable
 | 
			
		||||
let y = 123;
 | 
			
		||||
 | 
			
		||||
let x = 0;      // previous 'x' reused: not counted as new variable
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										62
									
								
								_archive/rhai_engine/rhaibook/safety/memory.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								_archive/rhai_engine/rhaibook/safety/memory.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
Limiting Memory Usage
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
During Evaluation
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
To prevent _out-of-memory_ failures, provide a closure to [`Engine::on_progress`][progress] to track
 | 
			
		||||
memory usage and force-terminate a malicious script before it can bring down the host system.
 | 
			
		||||
 | 
			
		||||
Most O/S provides system calls to obtain the current memory usage of the process.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
const MAX_MEMORY: usize = 10 * 1024 * 1024;   // 10MB
 | 
			
		||||
 | 
			
		||||
engine.on_progress(|_| {
 | 
			
		||||
    // Call a system function to obtain the current memory usage
 | 
			
		||||
    let memory_usage = get_current_progress_memory_usage();
 | 
			
		||||
 | 
			
		||||
    if memory_usage > MAX_MEMORY {
 | 
			
		||||
        // Terminate the script
 | 
			
		||||
        Some(Dynamic::UNIT)
 | 
			
		||||
    } else {
 | 
			
		||||
        // Continue
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
During Parsing
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
A malicious script can be carefully crafted such that it consumes all available memory during the
 | 
			
		||||
parsing stage.
 | 
			
		||||
 | 
			
		||||
Protect against this by via a closure to [`Engine::on_parse_token`][token remap filter].
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
const MAX_MEMORY: usize = 10 * 1024 * 1024;   // 10MB
 | 
			
		||||
 | 
			
		||||
engine.on_parse_token(|token, _, _| {
 | 
			
		||||
    // Call a system function to obtain the current memory usage
 | 
			
		||||
    let memory_usage = get_current_progress_memory_usage();
 | 
			
		||||
 | 
			
		||||
    if memory_usage > MAX_MEMORY {
 | 
			
		||||
        // Terminate parsing
 | 
			
		||||
        Token::LexError(
 | 
			
		||||
            LexError::Runtime("out of memory".into()).into()
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        // Continue
 | 
			
		||||
        token
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										99
									
								
								_archive/rhai_engine/rhaibook/safety/progress.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								_archive/rhai_engine/rhaibook/safety/progress.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
Limiting Run Time
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
[`Engine::on_progress`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_progress
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Track Progress and Force-Termination
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
```admonish info.side "Operations count vs. progress"
 | 
			
		||||
 | 
			
		||||
_Operations count_ does not indicate the _proportion_ of work already done – thus it is not real _progress_ tracking.
 | 
			
		||||
 | 
			
		||||
The real progress can be _estimated_ based on the expected number of operations in a typical run.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It is impossible to know when, or even whether, a script run will end
 | 
			
		||||
(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)).
 | 
			
		||||
 | 
			
		||||
When dealing with third-party untrusted scripts that may be malicious, in order to track evaluation
 | 
			
		||||
progress and force-terminate a script prematurely (for any reason), provide a closure to the
 | 
			
		||||
[`Engine`] via [`Engine::on_progress`].
 | 
			
		||||
 | 
			
		||||
The closure passed to [`Engine::on_progress`] will be called once for every operation.
 | 
			
		||||
 | 
			
		||||
Progress tracking is disabled with the [`unchecked`] feature.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Examples
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
### Periodic Logging
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
engine.on_progress(|count| {    // parameter is number of operations already performed
 | 
			
		||||
    if count % 1000 == 0 {
 | 
			
		||||
        println!("{count}");    // print out a progress log every 1,000 operations
 | 
			
		||||
    }
 | 
			
		||||
    None                        // return 'None' to continue running the script
 | 
			
		||||
                                // return 'Some(token)' to immediately terminate the script
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Limit running time
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
let start = get_time();         // get the current system time
 | 
			
		||||
 | 
			
		||||
engine.on_progress(move |_| {
 | 
			
		||||
    let now = get_time();
 | 
			
		||||
 | 
			
		||||
    if now.duration_since(start).as_secs() > 60 {
 | 
			
		||||
        // Return a dummy token just to force-terminate the script
 | 
			
		||||
        // after running for more than 60 seconds!
 | 
			
		||||
        Some(Dynamic::UNIT)
 | 
			
		||||
    } else {
 | 
			
		||||
        // Continue
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Function Signature of Callback
 | 
			
		||||
------------------------------
 | 
			
		||||
 | 
			
		||||
The signature of the closure to pass to [`Engine::on_progress`] is as follows.
 | 
			
		||||
 | 
			
		||||
> ```rust
 | 
			
		||||
> Fn(operations: u64) -> Option<Dynamic>
 | 
			
		||||
> ```
 | 
			
		||||
 | 
			
		||||
### Return value
 | 
			
		||||
 | 
			
		||||
|     Value     | Effect                                                                           |
 | 
			
		||||
| :-----------: | -------------------------------------------------------------------------------- |
 | 
			
		||||
| `Some(token)` | terminate immediately, with `token` (a [`Dynamic`] value) as _termination token_ |
 | 
			
		||||
|    `None`     | continue script evaluation                                                       |
 | 
			
		||||
 | 
			
		||||
### Termination Token
 | 
			
		||||
 | 
			
		||||
```admonish info.side "Token"
 | 
			
		||||
 | 
			
		||||
The termination token is commonly used to provide information on the _reason_ behind the termination decision.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The [`Dynamic`] value returned is a _termination token_.
 | 
			
		||||
 | 
			
		||||
A script that is manually terminated returns with the error `EvalAltResult::ErrorTerminated(token, position)`
 | 
			
		||||
wrapping this value.
 | 
			
		||||
 | 
			
		||||
If the termination token is not needed, simply return `Some(Dynamic::UNIT)` to terminate the script
 | 
			
		||||
run with [`()`] as the token.
 | 
			
		||||
							
								
								
									
										40
									
								
								_archive/rhai_engine/rhaibook/safety/sandbox.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								_archive/rhai_engine/rhaibook/safety/sandbox.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
Sand-Boxing – Block Access to External Data
 | 
			
		||||
=================================================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai is _sand-boxed_ so a script can never read from outside its own environment.
 | 
			
		||||
 | 
			
		||||
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including itself
 | 
			
		||||
(and therefore it is also _re-entrant_).
 | 
			
		||||
 | 
			
		||||
It is highly recommended that [`Engine`]'s be created immutable as much as possible.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
// Use the fluent API to configure an 'Engine'
 | 
			
		||||
engine.register_get("field", get_field)
 | 
			
		||||
      .register_set("field", set_field)
 | 
			
		||||
      .register_fn("do_work", action);
 | 
			
		||||
 | 
			
		||||
// Then turn it into an immutable instance
 | 
			
		||||
let engine = engine;
 | 
			
		||||
 | 
			
		||||
// 'engine' is immutable...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```admonish tip.small "Tip: Use Rhai to control external environment"
 | 
			
		||||
 | 
			
		||||
How does a _sand-boxed_, immutable [`Engine`] control the external environment?
 | 
			
		||||
 | 
			
		||||
This is necessary in order to use Rhai as a _dynamic control layer_ over a Rust core system.
 | 
			
		||||
 | 
			
		||||
There are two general patterns, both involving wrapping the external system
 | 
			
		||||
in a shared, interior-mutated object (e.g. `Rc<RefCell<T>>`):
 | 
			
		||||
 | 
			
		||||
* [Control Layer]({{rootUrl}}/patterns/control.md) pattern.
 | 
			
		||||
 | 
			
		||||
* [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										124
									
								
								_archive/rhai_engine/rhaibook/safety/stack.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								_archive/rhai_engine/rhaibook/safety/stack.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
Limiting Stack Usage
 | 
			
		||||
====================
 | 
			
		||||
 | 
			
		||||
{{#include ../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// 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) };
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish question.small "Negative values"
 | 
			
		||||
 | 
			
		||||
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`][progress] to
 | 
			
		||||
track stack usage and force-terminate a malicious script before it can bring down the host system.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
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`][token remap filter].
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
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
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
		Reference in New Issue
	
	Block a user