Programming in the ObjectCapabilityModel requires a style very similar to normal
ObjectOrientedProgramming, but there are differences. Some comments on this are
at <http://www.eros-os.org/pipermail/e-lang/2001-August/005539.html>.
For patterns useful in CapabilityOrientedProgramming, see CategorySecurityPatterns.
The following discussion
comes from http://www.combex.com/papers/darpa-report/html/index.html#unchecked ,
and is written in the context of a security review of a system using EeLanguage.
Explicit Differences between CapabilityOrientedProgramming and ObjectOrientedProgramming include:
- No static (global) mutable state is allowed. The EeLanguage enforces this, since there are no static mutables. This has a real impact on system design: an object such as the java.lang.System.out object cannot be created.
- Object instantiation sometimes requires more steps. In a capability system enforcing POLA [PrincipleOfLeastAuthority], there tend to be more levels of instantiation for an object: the work normally done by a single powerful constructor will, as a part of POLA, often be broken into a series of partial constructors as the final user of the constructed object gets just enough power to perform local customizations of the object. An explicit recognition of this multiple-level instantiation is the Author pattern followed by many emakers (E library packages). An E constructor will often run in the scope of an Authorizer that is first created and granted the needed authorities; then the constructor itself can be handed to less-trusted objects without having to give the less-trusted object the authorities needed to make the constructor. The most complex current example of this pattern is the FrameMakerMakerAuthor? in the CapDesk? PowerBox. An individual Caplet is granted an individual frameMaker for making windows. To create the frameMaker, there is first an authorization step in which authority to create JFrames (the underlying Swing windows) is granted. Then an intermediate Maker step customizes the frameMaker with an unalterable caplet pet icon and pet name so that the caplet cannot use its power to make frames to spoof the user.
- The Authorization step, and other intermediate levels of instantiation, can be disconcerting for the first-time capability programmer with an object-oriented background. It is, however, a simple extension of standard object-oriented practice. Indeed, between the start of this project and its completion we observed that the use of inner classes in Java has become increasingly ubiquitous in the example code from major vendors such as Sun and IBM. For the Java programmer who has become comfortable with these nested classes, the leap to multiple levels of instantiation is not even a speed bump, but more a pebble on the road. Meanwhile, the benefits of multi-layer POLA-oriented instantiation make it possible to be extremely confident, during debugging, that the majority of the library packages in a system could not have caused a surprising authority-requiring problem: one can see at a glance that the package did not have the authority, and move on to the next candidate for inspection.
- Facets and Forwarders are common patterns and must be easily supported. In capability programming, on the boundaries between trust realms, facets and revokable transparent forwarders are often used to grant limited access to powerful objects. The frameMaker above is an example, it is a facet on the Swing JFrame that does not allow the icon or the title prefix to be changed. Fortunately, the E programming language makes the construction of facets and forwarders painless. This implements the following user interface rule that is older than programming, and indeed, older than the printed word: "If you want someone to do something the right way, make the right way the easy way." Forwarders and facets have been made very easy. In E, the code for a general-purpose constructor for revokable forwarders can be written in as little as six lines of code:
def makeRevokableForwarderPair(obj) :any {
var innerObj := obj
def forwarder { match [verb, args] { E.call(innerObj, verb, args) } }
def revoker { to revoke() { innerObj := null } }
[revoker, forwarder]
}
- Encapsulation must be strictly enforced. As noted earlier, modularity discipline must be followed pervasively. It cannot be broken for convenience.
- In capability style, there can be no unchecked preconditions on an interface that may be exposed to clients in a distinct trust realm. If a client does not fulfill the preconditions in a contract with an interface, and if the implementation of the interface does not check and detect this failure, the results are unpredictable. Such unpredictability is the enemy of security, and cannot be tolerated. In the context of E, a large part of this principle can be implemented through the following E-specific rule:
- Rigorous guards [type declarations] should be imposed on arguments received across a trust boundary. Due to the nature of E semantics, a malicious component can send an object across the trust boundary which changes its nature as it is used, essentially spoofing the recipient. E has the most flexible and powerful dynamic type checking system yet devised for a programming language (using guards). However, to support rapid prototyping, these guards are optional in E. Therefore a best practice for E objects on the trust boundary is to impose the most restrictive guards possible on every argument received. Because of the DarpaBrowser? experience, an experimental feature has been implemented for E that would, on a module-by-module basis, allow the developer to require guards on all variables in the module. A PowerBox module, for example, should probably operate with this extra requirement imposed upon it.
Also see ObjectCapabilityModel, CapabilitySecurityModel.
CategorySecurity