[Python-Dev] Python jails (original) (raw)

Guido van Rossum guido at python.org
Sat Jun 11 02:44:26 CEST 2011


Hi Sam,

Have you seen this? http://tav.espians.com/paving-the-way-to-securing-the-python-interpreter.html

It might relate a similar idea. There were a few iterations of Tav's approach.

--Guido

On Fri, Jun 10, 2011 at 5:23 PM, Sam Edwards <sam.edwards at colorado.edu> wrote:

Hello! This is my first posting to the python-dev list, so please forgive me if I violate any unspoken etiquette here. :)

I was looking at Python 2.x's frestricted frame flag (or, rather, the numerous ways around it) and noticed that most (all?) of the attacks to escape restricted execution involved the attacker grabbing something he wasn't supposed to have. IMO, Python's extensive introspection features make that a losing battle, since it's simply too easy to forget to blacklist something and the attacker finding it. Not only that, even with a perfect vacuum-sealed jail, an attacker can still bring down the interpreter by exhausting memory or consuming excess CPU. I think I might have a way of securely sealing-in untrusted code. It's a fairly nascent idea, though, and I haven't worked out all of the details yet, so I'm posting what I have so far for feedback and for others to try to poke holes in it. Absolutely nothing here is final. I'm just framing out what I generally had in mind. Obviously, it will need to be adjusted to be consistent with "the Python way" - my hope is that this can become a PEP. :)

# It all starts with the introduction of a new type, called a jail. (I haven't yet worked out whether it should be a builtin type, ... # or a module.) Unjailed code can create jails, which will run the untrusted code and keep strict limits on it. ... j = jail() dir(j) ['class', 'delattr', 'doc', 'format', 'getattribute', 'hash', 'init', 'new', 'reduce', 'reduceex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'acquire', 'getcpulimit', 'getcpuusage', 'getmemorylimit', 'getmemoryusage', 'gettimelimit', 'gettimeusage', 'release', 'setcpulimit', 'setmemorylimit', 'settimelimit'] # The jail monitors three things: Memory (in bytes), real time (in seconds), and CPU time (also in seconds) ... # and it also allows you to impose limits on them. If any limit is non-zero, code in that jail may not exceed its limit. ... # Exceeding a memory limit will result in a MemoryError. I haven't decided what CPU/real time limits should raise. ... # The other two calls are "acquire" and "release," which allow you to seal (any) objects inside the jail, or bust them  # out. Objects inside the jail (i.e. created by code in that jail) contribute their sizeof() to the j.getmemoryusage() ... def stealPasswd(): ...         return open('/etc/passwd','r').read() ... j.acquire(stealPasswd) j.getmemoryusage() # The stealPasswd function, its code, etc. are now locked away within the jail. 375 stealPasswd() Traceback (most recent call last):  File "", line 1, in JailError: tried to access an object outside of the jail The object in question is, of course, 'open'. Unlike the frestricted model, the jail was freely able to grab the open() function, but was absolutely unable to touch it: It can't call it, set/get/delete attributes/items, or pass it as an argument to any functions. There are three criteria that determine whether an object can be accessed: a. The code accessing the object is not within a jail; or b. The object belongs to the same jail as the code accessing the object; or c. The object has an access function, and theObject.access(theJail) returns True. For the jail to be able to access 'open', it needs to be given access explicitly. I haven't quite decided how this should work, but I had in mind the creation of a "guard" (essentially a proxy) that allows the jail to access the object. It belongs to the same jail as the guarded object (and is therefore impossible to create within a jail unless the guarded object belongs to the same jail), has a list of jails (or None for 'any') that the guard will allow to access it (the guard is immutable, so jails can't mess with it even though they can access it), and what the guard will allow though it (read-write, read-only, call-within-jail, call-outside-jail). I have a couple remaining issues that I haven't quite sussed out: * How exactly do guards work? I had in mind a system of proxies (memory usage is a concern, especially  in memory-limited jails - maybe allow access to return specific modes of access rather than  all-or-nothing?) that recursively return more guards after operations. (e.g., if I have a guard allowing  read+call on sys, sys.stdout would return another guard allowing read+call on sys.stdout, likewise for  sys.stdout.write) * How are objects sealed in the jail? j.acquire can lead to serious problems with lots of references  getting recursively sealed in. Maybe disallow sealing in anything but code objects, or allow explicitly  running code within a jail like j.execute(code, globals(), locals()), which works fine since any objects  created by jailed code are also jailed. * How do imports work? Should import be modified so that when a jail invokes it, the import runs  normally (unjailed), and then returns the module with a special guard that allows read-only+call-within,  but not on builtins? This has a nice advantage, since jailed code can import e.g. socket, and maybe even  create a socket, but won't be able to do sock.connect(...), since socket.connect (which is running with  jailed permissions) can't touch the builtin socket module. * Is obj.access(j) the best way to decide access? It doesn't allow programmers much freedom to  customize the jail policy since they can't modify access for builtins. Maybe the jail should have  the first chance (such as j.access(obj)), which allows programmers to subclass the jail, and the jail  can fallback to obj.access(j) * How does Python keep track of what jail each frame is in? Maybe each frame can have a frame.fjail,  which references the jail object restricting that frame (or None for unjailed code) - frames' jails default  to the jail holding the code object, or can be explicitly overridden (as in j.execute(code, globals(), locals())) * When are jails switched? Obviously, jailed code called from unjailed code (or even from other unjailed  code) should be executed in the callee jail... But if a jailed caller is calling unjailed code, does the jail  follow, or does the unjailed code run in an unjailed frame? How do programmers specify that? ...that's pretty much my two (erm, twenty) cents on the matter. Again, any feedback/adversarial reasoning you guys can offer is much appreciated.


Python-Dev mailing list Python-Dev at python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/guido%40python.org

-- --Guido van Rossum (python.org/~guido)



More information about the Python-Dev mailing list