[Python-Dev] In defense of Capabilities [was: doc for new restricted execution design for Python]

Talin talin at acm.org
Sat Jul 8 05:29:40 CEST 2006

Brett Cannon wrote:
> On 7/7/06, Guido van Rossum <guido at python.org> wrote:
>> On 7/7/06, Brett Cannon <brett at python.org> wrote:
>> > I guess I am just not seeing where your approach is better than
>> preventing
>> > the constructor in 'file' and having open() return the 'file' object or
>> > proxy object.  With your approach 'file' would be flagged, but with the
>> > other you just put the same check in 'file's constructor.  With both 
>> you
>> > would probably also want open() to be a factory function anyway.  So I
>> don't
>> > see where you gain simplicity or more security.  It seems like you are
>> > pushing the check into the eval loop, but you still require the 
>> flagging
>> of
>> > objects as unsafe.  Going with the other two proposals you don't burden
>> the
>> > eval loop with the check but the objects that you would have flagged in
>> the
>> > first place.
>> >
>> > It just seems like we are pushing around the flagging of unsafe stuff
>> and
>> > that doesn't feel like it buys us much.
>> At the risk of repeating someone's point or making no sense (I'm only
>> following this with half an ear) I would like to point out that as
>> long as there's C code involved, we can have the equivalent of private
>> constructors in C++/Java. This means classes that cannot be
>> constructed by calling the class from Python. C code has to use some
>> other API to create an instance, bypassing this check. It seems
>> reasonable to me, even if most popular types *can* be constructed.
>> There are other types that have this property, e.g. list iterators.
>> Try type(iter(list()))().
> Good point.  C code could circumvent the bit check by doing all of the work
> behind the scenes without pushing the object on the stack.  But if the 
> check
> is in the C code for the object itself it is much harder to get around.
> -Brett

I may be confused (I usually am), but I think you are misinterpreting 
Guido's point. I think what he is saying is not "you should beware of C 
code getting around the checks" but rather he is pointing out that C 
code that gets around the checks can be a useful part of the system - 
thus the notion of "private constructors", in other words methods for 
creating an object that are normally inaccessible to Python code.

As to your point: I think I am beginning to understand, so let me 
reiterate to see if I have it right.

In the scenario you describe, the open() function is replaced by a 
function that returns a proxy that has limits on what it is allowed to 
do. (Ideally, it would return one of several different types of proxies 
based on the input file path - so a file handle to /tmp would have a 
different set of restrictions than a file handle to /home/me.)

Somewhere inside this proxy is the 'real' file handle. We need to insure 
that the proxy is air-tight, so that it can't 'leak' to the outside 
world. (The original motivation for my scheme was the belief that 
air-tightness couldn't be achieved, however the point that Guido makes 
above is beginning to make me believe otherwise.)

The proxy also has to be able to support all of the methods that the 
regular file handle can - because we're going to want to pass that proxy 
over to other subsystems that don't know that they are dealing with a 
proxy - such as XML parsers or config file readers. Because the 
permission checks are implemented in the proxy, this means that library 
code using the proxy has exactly the same access restrictions as the 
sandboxed code.

If you want any library code to be able to have additional privileges 
beyond what the sandboxed code can do, you'll need to pass them the 
'real' file handle, something which can only be done by either the 
proxy, or by a C 'gateway' function. This is not a problem as long as 
the library code doesn't attempt to hold on to the file handle 
(otherwise then the sandboxed code could potentially grab it from the 
library's objects.) So for any case where a library needs additional 
privileges, you'll need to add additional methods to the proxy or create 
the gateway functions to deal with that.

So if it truly is the case that the file handle cannot leak into the 
outside world, then you are correct - there's no need to put a check 
into the interpreter. My motivation was based on the belief that there 
would always be a way to get around that, so instead the notion was to 
'poison' these protected objects so that even if they did leak out, 
attempting to use them in a restricted environment would fail.

Moreover, there would be no need to even bother preventing such leakage 
- you wouldn't have to change the behavior of __subclasses__ and so on - 
which means that the 'restricted' environment would look nearly 
identical to the normal Python programming environment, i.e. you are 
free to inspect and use __subclasses__ or any other inspection feature 
as long as the result doesn't contain any poisoned objects.

(BTW, I'm going to dub my idea the 'poisoned object' scheme, just so we 
have a label.)

While I was typing this, I did realize a drawback to poisoned objects, 
which I will illustrate by the following example:

Suppose we want to grant to the sandboxed program permission to read and 
write cofiguration properties. We don't want to give them arbitrary 
write access to the file, instead we want to force the sandbox code to 
only access that file by setting and getting properties.

This is an example where a subsystem would require elevated privileges 
compared to the main program - the config file reader / writer needs to 
be able to read & write the file as a text stream, but we don't want to 
allow the sandboxed program to just write arbitrary data to it.

The only way to enforce this restriction is to re-wrap the 'real' file 
handle - in other words, replace the 'file-like object' wrapper with a 
'config-like object' wrapper.

Merely passing the poisoned file handle to 'config' doesn't work, 
because 'config' doesn't know how to safely handle it (only the C 
gateway code can shift the interpreter into a state where poisoned 
objects can be handled safely.)

Passing the file-like proxy to 'config' doesn't work either, because the 
proxy doesn't allow arbitrary writes.

The only thing that you really can do is write another wrapper - which 
is exactly what you would have to do in the non-poison case.

-- Talin

More information about the Python-Dev mailing list