Curiosity is bliss    Archive    Feed    About    Search

Julien Couvreur's programming blog and more

Capability-based security


Capability theory is an application of the principle of least authority to languages and operating systems. POLA (principle of least authority) is one of the well known security principles, along with "defense in depth" or "complexity is the enemy of security", amongst others.

There is a lot of capability theory papers online, mostly around the E language and EROS operating system. But still I couldn't get all the pieces together, so I turned to the E language mailing list. My questions were answered very quickly and led to some interesting discussions (most of which won't be covered here).

I'll try to summarize the key to what I learned: what are the criteria for a language to be considered capability secure?

Here are at least two criteria that distinguish a capability-oriented language like E:
- no ambient authority and no ambient API, only objects.
- memory safe (memory management and no pointer arithmetic).

Update: A third criteria is:
- private members should be inaccessible (perfect encapsulation).

No ambient authority
There is one major principle that derives from POLA: "there should be no ambient authority".
The reason for this principle comes from the observation that programs today are given way too much authority and that this authority is that of the user or principal. Basically, any program that I run as a user will carry all my authority: it'll be able to modify any file that I have access to, for example.

In practice, this means two things for a language: processes are spawned with no authority and the is no globally available API.

The first point is easier to grasp than the second. It means that if I receive a program in an email, if that program was a trojan, running it wouldn't do much damage because the program would not have access to all my files if I don't give it explicit access to them.

No global API, only objects
The second point has the most impact on the design of the language and the libraries and I will illustrate it with an example. In most languages, reading a file amount to using some global API like fopen (in C) or new (in Java). These are global in the sense that any program can call these from anywhere (you just need to include the appropriate namespace).

But in a capability oriented language, these APIs are simply not available globally, instead they are methods on objects. So if you want to read a file, you need to be provided a readable capability for the file first.

One nice side-effect of this strict object-oriented approach is that by limiting what capabilities or resources are available to a program it makes it easier to audit or security review it.
Another consequence of this design is that various modules within your program don't share the same authority, they only have the authority that is given them (no confused deputy problem).

Containement: memory management and no forgeable pointers
Different capability systems offer different levels of protection. Basically, it comes down to what is the smallest unit that can be compromised.
Let's say that one of the classes objects in your code has a bug that lets an attacker abuse it. Does that mean that the whole process is now controled by the attacker, or did the attacker only get the capabilities that the compromised object was holding (which probably are very limited)?

The first way of limiting the impact of the attack is to prevent stack attacks like buffer overflows. This is why E is memory safe (like Java): you can't have buffer overflows.

Another important language property that helps containing attacks is that the language shouldn't support forging object pointers. This means that the only way you can get a reference to an object is via a constructor or having another object return a reference to you. C on the other hand doesn't respect this, because it allows pointer arithmetic and int to pointer conversions.

That is how, in E, if an class object is compromised, the process isn't automatically compromised: the attacker can only do what that class object could and he can't get access to capabilities that are held in parts of the program that don't interact with that class object. You can compartimentalize your code modules by correctly designing your application.

And in the worst case, even if the whole process was "infected", a lot of risk has been mitigated because in practice most programs don't hold or need that much authority. On the other hand, in systems like Windows or Linux, if a process is compromised, it pretty much amounts to having the user's account compromised.

The shell and user interaction
In a capability system, processes can be made to run with only the authority they need. But there is at least one process that has pretty much all the user's authority: it's the shell. The shell needs to be able to start user programs and provide them with capabilities from the user's cap portfolio. So the shell is a sensitive program in terms of security, so it needs to be carefully designed (compartmentalized where possible) and reviewed.

A capability-oriented shell offers lots of room for user experience improvements when it comes to making sound security decisions.
Check out the Secure Interaction Design guidelines for user interfaces to make it simpler for users to keep their desktop safe.
Because sometimes the insecure component is between the keyboard and the screen, it is important that the user can follow simple rules like Granma's 6 Rules Of POLA.

Update: Thinking some more about the differences between "regular" languages and capability-based languages, I found one more major difference. It is very important in capability-based languages that private member variables really be inaccessible from the outside. An object should have strict control over the interface that it exposes and his internals should remain "secret".


Nice summary. I'd object to a couple of points:

On "No global API": you can have a library globally accessible, it just can't have any authority. So sqrt() would be okay to have available to anyone by default, while fopen() would not. fopen(), if we have it, must be in a privileged scope instead.

I'm not sure if you're saying that a capability system must not be vulnerable to buffer overflows. Disallowing that attack is good security, but not inherent to capabilities: you could have C programs still vulnerable this way, it's just that once you've taken over the process, you've 0wned only that process and the capabilities it holds (as you point out).

Posted by: Darius Bacon (September 27, 2003 12:42 AM) ______________________________________

Nice summary.

I think you mean "object" rather than "class" in the fourth-to-last paragraph. E has no classes!



Posted by: Zooko (September 27, 2003 05:05 AM) ______________________________________

Thanks for your feedback and corrections.
Memory safety is defintely a good security enhancement in any language. What I tried to say is that a capability-based language needs memory safety to reach a finner grain compartmentalization (the object).

Posted by: Dumky (September 27, 2003 02:24 PM) ______________________________________

I am pleased that you find the meaning of "ambient authority" clear. Some on the list didn't. I thought it was clear.
Regarding Darius Bacon's note: It would also be suitable to provide an fopen that takes a directory capability as parameter. But invoking the directory as an object is even better.

Posted by: Norm Hardy (September 28, 2003 08:40 AM) ______________________________________

"object" rather than "class"


Posted by: Dmitry Ikan (November 14, 2003 11:15 PM) ______________________________________

Thanks for the feedback. I just fixed the occurences of "class" that I had missed.

Posted by: Dumky (November 18, 2003 04:27 PM)
comments powered by Disqus