[Python-Dev] In defense of Capabilities [was: doc for new restricted execution design for Python]
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
>> > 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
>> > would probably also want open() to be a factory function anyway. So I
>> > 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
>> > objects as unsafe. Going with the other two proposals you don't burden
>> > eval loop with the check but the objects that you would have flagged in
>> > first place.
>> > It just seems like we are pushing around the flagging of unsafe stuff
>> > 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
> is in the C code for the object itself it is much harder to get around.
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
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.
More information about the Python-Dev