A sandbox is an isolated environment (a thread in our case); Things may go very badly wrong in the sandbox environment and not effect the environment that created it. This means that we must try very hard to limit the influence each environment has on the other. So the prototype and instructions of entry point Closures
are verified to ensure they will not reduce or break isolation.
In practice this means entry point closures must not:
- accept or return by reference
- accept or return objects
- execute a limited set of instructions
Instructions prohibited directly in the sandbox are:
- declare (anonymous) function
- declare (anonymous) class
- lexical scope access
- yield
No instructions are prohibited in the files which the sandbox may include, but allowing these instructions directly in the code which the sandbox executes at entry would break the isolation of the sandbox such that we couldn't be sure the system would remain stable.
With these restrictions in place, we can be sure that a sandbox may do anything up to but excluding making PHP segfault, and not effect the environment that created it.
- PHP 7.1+
- ZTS
- <pthread.h>
final class sandbox\Runtime {
/**
* Shall construct a new sandbox thread
* @param array optional ini configuration
* @throws \sandbox\Exception if the sandbox could not be created
*/
public function __construct(array $ini = []);
/**
* Shall enter into the thread at the given entry point
* @param entry point for sandbox
* @param argv for closure
* @throws \sandbox\Exception if $closure or $argv are not valid
* @throws \sandbox\Exception if $closure bails
**/
public function enter(Closure $closure, array $argv = []) : mixed;
/*
* Shall close the sandbox
* @throws \sandbox\Exception if the sandbox is unusable
*/
public function close();
}
The configuration array passed to the constructor will configure the sandbox using INI.
The following options may be an array, or comma separated list:
- disable_functions
- disable_classes
The following options will be ignored:
- extension - use dl()
- zend_extension - unsupported
All other options are passed directly to zend verbatim and set as if set by system configuration file.
PHP isn't really share nothing, it's share as little as possible to get the job done!
It's possible to load extensions in the sandbox that are not available in the parent runtime, however this comes with a (benign, mostly) quirk.
$sandbox = new \sandbox\Runtime();
$sandbox->enter(function(){
dl("componere");
var_dump(new \Componere\Definition("mine"));
});
if (extension_loaded("componere")){
var_dump(new \Componere\Definition("mine")); # line 13
}
The code above will output something like:
object(Componere\Definition)#1 (0) {
}
Fatal error: Uncaught Error: Class 'Componere\Definition' not found in %s:13
Stack trace:
#0 {main}
thrown in %s on line 13
The reason for this behaviour is that the extension registry is a truly global symbol, and so the parent context does detect that the extension is loaded, but it is not able to detect that it was never started in the this context and so did not register any classes.
The dl
function may be disabled by configuration in the normal way.