Security by capabilities is uncontroversial, and even orthodox, judging by its ubiquity in new systems. (Operating systems take an unusually long time to adopt new ideas, due to their strong network effects, so it's easy for an idea to be orthodox without being used in any popular system.) One particularly popular variant – at least in research, if not in deployed systems – is the object-capability system, in which having a capability is identified with having a reference to an object.
It's easy to see why this is popular: it elegantly exploits the existing properties of object reference to provide powerful security guarantees without doing anything to explicitly track capabilities.
Unfortunately, it tends to be incompatible with reflection, and especially with heap-traversal operations like Squeak's nextObject
, which allows iterating through all objects in memory. If you can see arbitrary objects, you can see arbitrary capabilities, and the object-capability system is useless.
Elegance often means making one component do several things. Sometimes this works well, but sometimes the component can't support all the loads placed on it. I think object capabilities are a case of this. They make the object-reference graph do the work of tracking who has what capabilities, but this works only if programs can't do much to modify the graph.
E has this problem twice: not only does it use reference for capabilities, it uses ordinary message passing as its mechanism for calling between security domains, relying on lack of reflection to prevent untrusted code from doing anything more than send messages. There's no access control on messages, so modules generally expose their untrusted interfaces through proxy objects which understand only public messages. Reflection would let programs see past the proxies to the objects behind them, allowing them to send messages to internal objects and defeating the domain boundary.
Reusing basic language features for security is seductively simple, but it's dangerous — once language semantics are security-critical, it's hard to extend them safely.
A couple points:
ReplyDelete(1) Reflection is not incompatible with capability security. It requires only that reflection itself be a capability. This is done in Gilad Bracha's Newspeak language, where the technique is called Mirrors. Similar could be done for heap-traversal capabilities, though I'd rather just eliminate that misfeature.
(2) Extensibility does not require reflection. In a language with ambient authority, subprograms can bind directly to their environments so reflection might seem like the only mechanism to reach in deeply and tweak things. But in a capability-secure language, authority flows from a central point (e.g. using a powerbox pattern), and it is quite feasible to inject observers and auditors on authority used by a subprogram.
It seems you are reasoning about a new paradigm in terms of your old patterns. Learn the new idioms and patterns.
"Ambient authority" is a perjorative term for capabilities you can rely on. E programs can rely on being able to send messages to any object they have a reference to, and to use the basic parts of the standard library. Theoretically an E program could be run without access to e.g. integer arithmetic, but if such an environment broke a program, one would blame the environment, not the program, so programs can rely on integer arithmetic. (It would be difficult to write programs otherwise.) Thus, E does effectively have ambient authority. It's much less authority than most environments provide, but this is a difference of degree, not kind. A system with no ambient authority is a system where you can't rely on being able to do anything. (If you consider this an abuse of the term "ambient authority", try replacing it with something like "base authority".)
DeleteThe reason features like reflection are incompatible with capability security is that they can't be part of the ambient/base authority. They can be provided as capabilities, but in that case they're no longer usable as general-purpose tools, because you can't rely on having them. (Similarly, programs can't usually rely on having root privileges.) This is OK for FFI, but not for basic tools like reflection.
It might work to offer an ambient reflection capability that worked on ordinary objects but not on "interface objects" like proxies. That would be good enough for uses like persistence, though probably not for things like method dispatch (but E already provides that) and typecase.
It is true that you can 'rely' on ambient authorities. However, you imply that you cannot rely on granted capabilities, which is not true. In general, if a subprogram cannot perform its job and fails because the caller offers insufficient capabilities, that is ultimately the caller's problem. It was done with the caller's authority, after all. In a typed language, forgetting to provide a capability could be treated as a type error. A subprogram can depend on the capabilities it requires.
DeleteThe idea that E has ambient authority is not new to me. I believe E offers too much ambient authority with respect to the 'new': every subprogram can create 'new' local state and unique identity. If state were provided through capabilities, we immediately gain both greater security (auditing, purity where desired) and a very easy approach to orthogonal persistence, undo and rewind, branching, and observer extensions - all without need for reflection. What "extensibility" means to me is the ability to impact or build upon the deep behavior of a subprogram without invasively modifying it. Reflection is one way to achieve extensibility. Capabilities offer another way.
Regarding your "reflection should be ambient" assumption, I would argue the contrary. Reflective systems normally have this problematic 'discontinuity' between objects inside the system and objects outside of it. Capabilities can enable partitioning the model and handling reflection more precisely, such that working internal to the language is similar to working outside. We could for example support mockup environments.