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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Configuration of runtime execution restrictions
//!
//! The `RestrictConfig` instance within an execution context configures
//! the restrictions imposed on code execution. These restrictions may include
//! limiting execution time and memory consumption.
//!
//! However, use of `RestrictConfig` is not necessarily sufficient to isolate
//! an execution environment from the host system. Other steps should be taken,
//! such as:
//!
//! * Installing a `GlobalIo` instance that does not allow access to
//!   system `stdout` and `stdin` streams.
//! * Installing a `ModuleLoader` instance that restricts access to the
//!  filesystem and whitelists a small set of "safe" builtin modules.
//!
//! # Example
//!
//! ```
//! use std::rc::Rc;
//! use ketos::{Builder, GlobalIo, BuiltinModuleLoader, RestrictConfig};
//!
//! let interp = Builder::new()
//!     .restrict(RestrictConfig::strict())
//!     .io(Rc::new(GlobalIo::null()))
//!     .module_loader(Box::new(BuiltinModuleLoader))
//!     .finish();
//!
//! // ...
//! # let _ = interp;
//! ```

use std::fmt;
use std::time::Duration;

use name::{NameDisplay, NameStore};

/// Contains parameters configuring restrictions of runtime code execution
///
/// See [module-level documentation](index.html) for an example of its use.
#[derive(Clone, Debug)]
pub struct RestrictConfig {
    /// Limits the maximum execution time, beginning from a call into the
    /// virtual machine, until the topmost function returns.
    ///
    /// If the user desires to limit total execution time of multiple separate
    /// function calls, the user must track execution time and adjust this
    /// limit manually.
    pub execution_time: Option<Duration>,
    /// Limits the call stack depth during execution to a number of nested
    /// functions calls
    pub call_stack_size: usize,
    /// Limits the number of values that can be stored on the stack during
    /// execution
    pub value_stack_size: usize,
    /// Limits the maximum number of values that can be stored in a `GlobalScope`
    pub namespace_size: usize,
    /// Memory limit during execution of code.
    /// This is not a specific measure of bytes; it's more an abstract
    /// estimate of values held during execution.
    pub memory_limit: usize,
    /// Maximum size, in bits, of integer and ratio values
    pub max_integer_size: usize,
    /// Maximum nested depth of syntactical elements
    pub max_syntax_nesting: usize,
}

/// Represents an error caused by breach of runtime execution restrictions
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RestrictError {
    /// Execution time exceeded limit
    ExecutionTimeExceeded,
    /// Call stack exceeded limit
    CallStackExceeded,
    /// Value stack exceeded limit
    ValueStackExceeded,
    /// Namespace size exceeded limit
    NamespaceSizeExceeded,
    /// Memory consumption exceeded limit
    MemoryLimitExceeded,
    /// Integer bit limit exceeded
    IntegerLimitExceeded,
    /// Nested syntax exceeded limit
    MaxSyntaxNestingExceeded,
}

impl RestrictError {
    /// Returns a string describing the error that occurred.
    pub fn description(&self) -> &'static str {
        use self::RestrictError::*;

        match *self {
            ExecutionTimeExceeded => "execution time exceeded",
            CallStackExceeded => "max call stack exceeded",
            ValueStackExceeded => "max value stack exceeded",
            NamespaceSizeExceeded => "max namespace size exceeded",
            MemoryLimitExceeded => "max memory limit exceeded",
            IntegerLimitExceeded => "integer size limit exceeded",
            MaxSyntaxNestingExceeded => "max syntax nesting exceeded",
        }
    }
}

impl fmt::Display for RestrictError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.description())
    }
}

impl NameDisplay for RestrictError {
    fn fmt(&self, _names: &NameStore, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

/// Maximum size of call stack, with permissive configuration.
pub const PERMISSIVE_CALL_STACK_SIZE: usize = 1024;

/// Maximum size of value stack, in values, with permissive configuration.
pub const PERMISSIVE_VALUE_STACK_SIZE: usize = 4096;

/// Maximum size of call stack, with strict configuration.
pub const STRICT_CALL_STACK_SIZE: usize = PERMISSIVE_CALL_STACK_SIZE / 16;

/// Maximum size of value stack, in values, with strict configuration.
pub const STRICT_VALUE_STACK_SIZE: usize = PERMISSIVE_VALUE_STACK_SIZE / 16;

impl RestrictConfig {
    /// Returns a `RestrictConfig` that is most permissive.
    ///
    /// No restrictions are placed on executing code.
    pub fn permissive() -> RestrictConfig {
        RestrictConfig{
            execution_time: None,
            call_stack_size: PERMISSIVE_CALL_STACK_SIZE,
            value_stack_size: PERMISSIVE_VALUE_STACK_SIZE,
            namespace_size: usize::max_value(),
            memory_limit: usize::max_value(),
            max_integer_size: usize::max_value(),
            max_syntax_nesting: usize::max_value(),
        }
    }

    /// Returns a `RestrictConfig` that is most strict.
    ///
    /// Small programs with short runtimes should not have a problem operating
    /// within these restrictions.
    pub fn strict() -> RestrictConfig {
        RestrictConfig{
            execution_time: Some(Duration::from_millis(100)),
            call_stack_size: STRICT_CALL_STACK_SIZE,
            value_stack_size: STRICT_VALUE_STACK_SIZE,
            namespace_size: 32,
            memory_limit: STRICT_VALUE_STACK_SIZE,
            max_integer_size: 100,
            max_syntax_nesting: 32,
        }
    }
}