[Python-Dev] Re: Capabilities

Samuele Pedroni pedronis@bluewin.ch
Mon, 10 Mar 2003 15:18:43 +0100


From: "Jim Fulton" <jim@zope.com>
> How can it be used to break out of restricted execution?
>
> I see three risks:
>
>    1. The exception provides methods to do harmful things,
>       such as create side effects or provide access to data outside
>       the exception.
>
>    2. The exception creates data that needs to be protected.  For example
>       Zope uses a NotFoundError exception taht contains an object being
searched.
>
>    3. The exception methods meta data provide access to module globals.
>
> Risk 1 needs to be mitigated through proper exception design. Exceptions
> need to be limited in what their methods do.  This is a bit brittle, but
> all standard exceptions have this property.
>
> Risk 2 is mitigated by proxying exception instance data.  Proxies can do
this.
> This is what we've decided to do, although we haven't implemented it yet.
>
> Risk 3 is mitigated by restricted execution.
>
> Have I missed anything?

OK, I have had the time to really try what I was thinking about. I have not
found a way to really break out from restricted execution
(does not mean I'm sure there isn't) BUT:

I'm considering:
- Python 2.2.2
- Zope 3 3.0a1 and
  zope.security.interpreter.RestrictedInterpreter
  with zope.security.simplepolicies.ParanoidSecurityPolicy (the default)

so

1. a bug (rexec had it too). If I remember correctly the solution is
re-injecting __builtins__ before each exec

C:\transit\Zope3-3.0a1\src\zope\security>\usr\python22\python
Python 2.2.2 (#37, Oct 14 2002, 17:02:34) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append('..\..')
>>> from zope.security.interpreter import RestrictedInterpreter
>>> ri=RestrictedInterpreter()
>>> ri.ri_exec("class A: pass")
>>> ri.ri_exec("print A.__dict__")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "..\..\zope\security\interpreter.py", line 32, in ri_exec
    exec code in self.globals
  File "<string>", line 1, in ?
RuntimeError: class.__dict__ not accessible in restricted mode
>>> ri.ri_exec("del __builtins__")
>>> ri.ri_exec("print A.__dict__")
{'__module__': '__builtin__', '__doc__': None}

or be sure to call ri_exec only once on each RestrictedInterpreter instance.

Assuming that fixed:

2. If code executed under a RestrictedInterpreter could obtain a MyExc instance
and had a working unproxied/non-proxying 'property' built-in, it could very
likely break out from restricted execution. Fortunately the 'property' passed
to such code is not working. Given that that's not the case I skip the
illustration.

3.  How much this scenario is likely really depend on how RestrictedInterpreter
is used, how and where exceptions are defined, if really restricted code can
manage to get an instance of one of them ... if further restrictions e.g. on
subclassing are added or removed ... if the general situation of restricted
execution and new-style classes improve. All of this I don't know.

Here I consider: a "dangerous" function ('sys.exit') is imported in the same
module where MyExc is defined, MyExc is not defined under restricted execution,
a proxied function is passed to restricted code such that it can capture an
instance of MyExc (as I said whether this set of things is likely/unlikely I
don't know):

<s.py>
import sys

from sys import exit # !!! same module as MyExc

sys.path.append('C:/transit/Zope3-3.0a1/src')

from zope.security.interpreter import RestrictedInterpreter
from zope.security.checker import ProxyFactory

class MyExc(Exception): # !!! definition outside of resticted execution
  def __init__(self,msg):
    self.message = msg
    Exception.__init__(self,msg)

  def __str__(self):
    return self.message

def myfunc():
  raise MyExc('foo')

ri = RestrictedInterpreter()

ri.globals['myfunc'] = ProxyFactory(myfunc)

f = open('c:/Documenti/x.txt','r')
code = f.read()
f.close()

ri.ri_exec(code)

print "OK"
</s.py>

Anyway I have a _very baroque_ x.txt that  manages to call sys.exit.

regards