[Python-Dev] Python jails

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 f_restricted 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__', '__reduce_ex__', '__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 "<stdin>", line 1, in <module>
> JailError: tried to access an object outside of the jail
>
> The object in question is, of course, 'open'. Unlike the f_restricted
> 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.f_jail,
>    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