Introduction
Almost all extensions I (PascalBauermeister) wrote are using eval to parse parameters.
I know that it is a potentially (very) dangerous thing to do, but on the other hand it unleashs a great deal of flexibility.
As I was expecting, some feedback came. FlorianFesti wrote about ProcessorMarket/if.py:
This thing is not suited for a publicly editable Wiki! It uses Python's eval() function. Pascal consider implementing a simpler query language which is less dangerous. (Try using the following expression: (([0]*1000000)*1000000)*1000000) -- FlorianFesti 2005-12-15 09:04:23
See my feedback below.
Security measures
The Python eval function is described there: http://docs.python.org/lib/built-in-funcs.html#l2h-23
The code uses eval this way (example of ProcessorMarket/if.py):
So there are 3 things cared for:
no builtin functions allowed (solves issue described in MacroMarket/SearchInPagesAndSort/SecurityHole)
- access to well-defined objects, one variable and one function in the above example
- enclosed in a try-except statement
When applied to ProcessorMarket/if.py, this:
{ { {#!if (([0]*1000000)*1000000)*1000000 Something #else Something else } } }
simply displays "Something else" (MemoryError exception gets caught).
But there are certainly other issues I don't see now...
This simply run for long time, without memory error:
eval('10 ** 10000000000000000000000')
These also seems to run forever, consuming all memory (1.5G installed), trashing, no error:
eval('zip(["x" * 10000000] * 100000000, ["x" * 10000000] * 100000000)') eval('dict([("x" * 100000000, "x" * 1000000000)] * 100000000)') eval('dict([((None, None) * 100000000, (None, None) * 100000000)] * 100000000)')
eval is lot of fun, do use it
Comments and discussion
Even if you set builtins to an empty list, there are still lots of things the code can do in a single expression. This is from a post I sent to the Python mailing list:
- Try this expression in Python 3.0:
1 [x for x in ().__class__.__base__.__subclasses__() if x.__name__ == '_FileIO'][0]('hello.txt', 'w').write('Hello, World!')
To explain, ().__class__.__base__.__subclasses__() gives you a list of all object-derived classes, i.e., of all classes that exist in the surrounding program. If you can find just one class that allows you to do something subtle or dangerous, you're done. See also:
"Controlling Access to Resources Within The Python Interpreter" (http://people.cs.ubc.ca/~drifty/papers/python_security.pdf)
To write your own restricted expression parser, the standard module "ast" is quite useful. For example, ast.parse("my_number < 10") gives you a syntax tree similar to this:
From there, you can implement your own logic to traverse the tree, which gives you very fine-grained control over the kinds of expressions to allow, where to look up variable names, how to react to errors, etc.1 ast.Expr(ast.Compare(ops=[ast.Lt], left=ast.Name(id="my_number"), comparators=[ast.Num(n=10)]))
-- 91.143.104.101 2010-01-09 15:05:39
I am currently developing a very concise parser inspired from http://effbot.org/zone/simple-iterator-parser.htm
-- PascalBauermeister 2010-01-09 16:55:06