Evoque supports
python expressions only. This means that
python statements are already naturally restricted as
when evaluated these will always raise a
SyntaxError
.
The problem of restricting execution is thus reduced
to the problem of making the eval
python built-in
safe. This is by no means an easy task, but at least with its
more limited scope it is simpler than attempting to
generically restrict or sandbox the python interpreter.
Running in restricted mode will (supposedly)
make it impossible to manipulate tangible resources,
such as files and sockets, from within a template.
Protection of intangible resources,
such as memory and CPU usage, is not (yet) provided (see below).
restricted: bool = False
This is the one domain init parameter that when set
to True
will initialize the domain to
run in restricted execution mode.
Setting this to True
has therefore the following
consequences:
1. Sets a dummy __builtins__
Sets a dummy __builtins__
empty dict on the
domain-wide globals dict used by eval.
2. No builtins that are deemed unsafe
For every builtin that is not deemed unsafe, will
add a top-level entry to the domain-wide globals dict used by eval.
Python (2.4, 2.5, 2.6, 3.0) __builtins__
currently considered unsafe
are (defined in the list Domain.DISALLOW_BUILTINS
):
DISALLOW_BUILTINS = ["_", "__debug__", "__doc__", "__import__", "__name__", "buffer", "callable", "classmethod", "coerce", "compile", "delattr", "dir", "eval", "execfile", "exit", "file", "getattr", "globals", "hasattr", "id", "input", "isinstance", "issubclass", "locals", "object", "open", "quit", "raw_input", "reload", "setattr", "staticmethod", "super", "type", "vars"]
In addition to the above, all subclasses of BaseException
(or, in Python 2.4, of Exception
) are considered
potentially unsafe and so are not made available
as entries on the globals dict.
This may seem overly restrictive, but then templating should not
require more than a small subset of python’s
possibilities, for convenience of doing simple progamming tasks
e.g. enumerating over a list.
3. Runtime scan of all expressions
At runtime, all string expressions to be evaluated,
necessarily via restrictedEvaluator[expression]
,
are first scanned for any disallowed attribute lookups,
and if a string expression matches, a LookupError
will be raised. The restricted_scan
pattern will match
any attempt to access any attribute that starts with one of the
following character sequences:
restricted_scan = re.compile(r"|\.\s*".join([ "__", "func_", "f_", "im_", "tb_", "gi_", "throw"]), re.DOTALL)
Note that expression-building trickery will not achieve anything,
as expressions are never evaluated twice i.e. an expression
may build a string but such a string cannot in anyway
be evaluated. To illustrate, consider an expression such as
${legit_obj.__subclasses__()}
.
This will fail
because it attempts an attribute lookup that starts with “__”.
Attempting to bypass this by doing something like
${"legit_obj." + "_"*2 + "subclasses" + "_"*2 + "()"}
will simply return the string
"legit_obj.__subclasses__()"
that cannot be re-rendered.
4. No re-evaluation of expressions
Evoque takes every precaution possible to make it impossible to
re-evaluate rendered results in any way.
For example, Evoque templates categorically cannot arbitrarily
set a variable, or initialize a new string-based template.
Furthermore, under restricted mode, builtins such as
eval
or getattr
are of course
not available.
Is this enough?
Depending on paranoia level, probably not.
Besides more testing to validate that the above effectively
does not allow the template programmer to access and manipulate
any tangible resources, it should still be possible to
bring down the interpreter via DOS maliciousness,
e.g. evaluating an expression for a very large
multiplication to consume all available memory, or to take
a very long time to finish.
Brett Cannon and Eric Wohlstadter, from UBC in Vancouver, have done
some thorough research into the feasibility of more ambitiously
applying a similar but more generalized approach to fully restrict
the python interpreter i.e. all possible expressions and statement
constructs —
they have summarized their work in this inspirational paper:
Controlling Access to Resources Within The Python Interpreter
Nonetheless, further runtime analysis of expressions will be required
to protect against intangible resources such as memory and
CPU usage, and how best to add such
protection is future work that is still to be done.
Thus, for the paranoid, restricted execution mode should for now
best be considered experimental.