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.edwa...@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@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) _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com