[Python-Dev] capability-mediated modules (was: python-dev Summary for 2003-03-01 through 2003-03-15)

Zooko zooko@zooko.com
Tue, 18 Mar 2003 07:41:23 -0500


brett@python.org wrote:
>
> Security with capabilities is done based on possession; if you hold a 
> reference to an object you can use that object.

Note that you can use capabilities as your sole access control mechanism if 
every resource that you want to protect is identifiable with a Python reference.

For example, suppose you want to control the ability to listen on sockets for 
network traffic.  If there is a reference (e.g., to an object) that represents 
the privilege of listening on sockets, then you can give such a reference to one 
object, allowing that object it to listen on sockets, while withholding it from 
another object, thus preventing that one from listening on sockets.

The only part of Python that isn't already well-matched with capabilities is the 
way that authority is gained by importing modules, and you can load a module 
even when you weren't given a reference to it.  Reconciling Python modules with 
capabilities would be the challenging part.  Python objects, bound methods, 
functions, and suchlike are already well-matched to capabilities.

In case that isn't clear, I'll write a quick example.  Recalling my "tic-tac-toe 
game" example from [1], I wrote code which allowed or denied the tic-tac-toe 
game to paint a window on the screen and to write to a file.  This "allow-or-
deny" enforcement was unified with the designation of which window and which 
file.  That is: by passing a reference to a certain window, I simultaneously 
told the tic-tac-toe game which window to draw in *and* extended to it the 
privilege of drawing to the screen.

This is a central motto of the capability security crowd: "Unify designation 
with authority."

In a capability system *all* authority -- everything that you could ever want to 
prevent -- is mediated by capabilities.  Code that is loaded and run, but to 
which no capabilities are extended, must be incapable of doing anything 
dangerous.

Now, what about modules?  In current Python, some code can "import os" and gain 
all kinds of authority.  In the rexec scheme, as I understand it, there was a 
handler function which could be overridden to determine what happens when I try 
"import os".  This is effectively a "policy mini-language", such as in the 
hypothetical "restricted Python v2" [1].

(Guido has pointed out that this overridable policy handler could be used to 
implement capabilities as well as other regimes.  I think I agree in principle, 
but what I am advocating here is having the core language implement capabilities 
so that the programmer-visible part is as minimal and unified as possible.)

Now what I would *like* is that instead of doing "import os" to load code, 
instead the caller provides, or doesn't provide the os module as part of the 
construction/invocation of A.

I don't have a clear idea yet of how that could be implemented in a Pythonic, 
compatible way.

Just to help me think about it I'll suggest a non-Pythonic and incompatible way: 
there is no "import" keyword.  When you invoke a constructor, function, method, 
etc., you have to pass as arguments references to everything that the code will 
need to do its job.  So, assuming the tic-tac-toe game requires the "math" 
module and the "string" module, I would have to write:

# restricted Python v3+modules
game = TicTacToeGame()
game.display(open("/tmp/tttgame.out","w"), math, string)

The burden of typing in dozens of module names with each invocation can be eased 
by: 1. bundling modules together (put math, string, and some other stuff into 
one object/module/package named "standardstuff" and pass that as an argument), 
2. "safe" modules that nobody could ever wish to prevent could be globally 
available (via the resurrected "import" keyword, I guess).

If math and string are both "safe", then the example goes back to:

# restricted Python v3+modules
game = TicTacToeGame()
# a game against itself that writes results to a file
game.display(open("/tmp/tttgame.out","w"))

# a game against remote, listening on a socket
game.display(open("/tmp/tttgame.out","w"), socket)

(Note that there is a bootstrapping problem -- *some* code has to receive a 
reference to the os module ex nihilo.  That code should be "trusted" code -- the 
Python interpreter, basically.)

Ah, but this last line shows another problem -- the game now has the socket 
module, and the ability to open sockets to remote hosts and more.  I just wanted 
to allow it to listen on a particular port!  The code would be safer if I didn't 
pass the large-grained module and instead passed a specific object:

# a game against remote, listening on a socket
listensocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listensocket.bind(('daring.cwi.nl', 8901,))
game.display(open("/tmp/tttgame.out","w"), listensocket)

Regards,

Zooko

http://zooko.com/
         ^-- under re-construction: some new stuff, some broken links

[1] http://mail.python.org/pipermail/python-dev/2003-March/033938.html