[E-Lang] Rights Amplification: The Next Layer Up

Mark S. Miller markm@caplet.com
Tue, 05 Sep 2000 17:30:07 -0700


[Dean, please read carefully.  It's rather different than what we agreed on.]


                                         The Problem


Now that we've committed to Java 2, I'm taking the opportunity to rewrite 
the implementation of Object-Pluribus (the "proxy-comm" system) to take 
advantage of the new weak pointers.  The rewrite is looking substantially 
simpler than the current implementation, and it has full message pipelining!  

Of course, any rewrite is an opportunity for opportunities.  The biggest 
thing missing from E is persistence, and both persistence and comm have at 
their core serialization. Objects need to serialize themselves very 
differently to these two streams: objects encapsulating precious secrets or 
authority need to not serialize them to the comm stream, of course, as that 
is being transmitted to untrusted machines.  OTOH, it is these precious 
things that most desperately want to be serialized to the persistence 
stream, so they won't disappear when Windows crashes.

So far, E has been built with special knowledge of these two in the TCB.  
However, a rewrite is also an opportunity for shrinking the TCB.  The comm 
system was designed to not need any special privileges, and indeed, that is
how it should be run since it represents untrusted other machines.  
Therefore, I've been trying to write it so it can be written in E, and (once 
there's an E compiler) I will try to rewrite it again in regular 
unprivileged E code.

So instead of two serialization subsystems (comm & persistence), we can have 
any number.  We need to enable objects to serialize themselves differently 
depending on which subsystem is asking.  However, the protocol enabling this 
customization must not be abusable by other objects.  In particular, a 
normal client of an object must not be able to say 
     "Please serialize your crown jewels on to this here stream.  It really 
      is the persistence stream.  Trust me."  
On the other hand, the persistence subsystem itself must find a 
way to make essentially this same request, and be believed.


                                          The Solution


The answer, of course, is rights amplification.  E's sealer/unsealer pairs 
already provide most of the kernel mechanism needed.  The remaining kernel 
mechanism, presented below, provides a universal method for attempting to 
rights-amplify any object: getOptSealedFacet/1.  (A universal method, or a 
Miranda Method, is one that all objects are assumed to respond to.)  This 
ability is useful for many "meta-level" issues besides persistence:

* equality: sameness testing & hashing
* verifying an object instantiates an audited expression
     and therefore has the properties that auditor ensures
* equivalent of Java's package-scope pattern of rights amplification
* debugging (obviously requires rights amplification)
* application-specific needs for rights amplification, as in the MintMaker's 
   purses.  (It's really ugly for normally clients to see that there's a 
   getDecr method, but be unable to use it.)
   http://www.erights.org/elib/capability/ode/ode-capabilities.html#simple-money 
   

The rest is just normal non-universal protocol design, patterns of use, and 
some syntactic sugar.

(A naming note: I use "opt" for "optional" in the names of methods which may 
return "null" to indicate absence of an answer, and I use "opt" in the names 
of variables with may hold "null" to indicate absence of a value.  Note that 
"map get(foo)" is ok, even if "foo" may map to "null", since the "null" is a 
valid answer, rather than the absence of an answer.)



                  New Universal Method: getOptSealedFacet(brand)


We define one new universal method.  When written without sugar, a method 
body would typically look something like:

     to getOptSealedFacet(brand :Brand) :(nullOk(SealedBox)) {
         switch (brand) {
             match ==(FooSealer brand) {
                 FooSealer seal(...my facet for Foo...)
             }
             match ==(BarSealer brand) {
                 BarSealer seal(...my facet for Bar...)
             }
             # simple story used for now, to be corrected later
             # match _ { null }

             # the real story, to be explained later
             match _ { meta getDefaultOptSealedFacet(self, brand) }
         }
     }


The request with the Foo brand means, 

     "Gimme a box which will unseal with the FooUnsealer.  (Ie, a box sealed by 
     the FooSealer.)  Inside the box should be whatever you feel appropriate 
     to provide to me, assuming I actually have the FooUnsealer.  If you can't 
     (because you don't have the FooSealer) or won't, return null."  

The Foo brand therefore represent a particular request, just as the unary 
message name "foo" would.  Having the FooSealer represents the ability to 
meaningfully answer these requests.  Having the FooUnsealer represents the 
ability to meaningfully ask these requests.

A server (the one receiving the request) without a FooSealer can still react 
to the making of the request, just not in a way that will satisfy to the 
client.  However, the client must understand that such a server may still 
grab the brand and engage in other side effects, so a failed request may 
still have effects.  Neither of these should be problems for clients, as 
long as they are aware of the issue.

On the flip side, while the server does know that the request came from a 
client (someone who could send to the server), the server must not assume 
anything more.  In particular, the server must not assume the request came 
from someone with a FooUnsealer.  Therefore, the server should not perform 
any side effects at all in its getOptSealedFacet method itself, but rather 
only in the objects that this method places inside the sealed boxes.  This 
should not be a problem for servers, as long as they are aware of this issue.


                                         Uses


Returning to serialization, a serialization subsystem would normally make 
its Sealer widely available but keep its Unsealer to itself.  Sealers that 
are part of "system" services, such as comm and persistence, would be made 
available in the "universal" name space.  And object that wanted to 
customize its serialization differently on each of these may write:

             match ==(CommSealer brand) {
                 CommSealer seal(...my ambassador...)
             }
             match ==(PersistenceSealer brand) {
                 PersistenceSealer seal(...my next incarnation...)
             }

to return an object to be serialized in lieu of itself.  The replaceObject() 
method of a given serialization stream may do:

             to replaceObject(original) :any {
                 def optBox := original getOptSealedFacet(myUnsealer brand)
                 if (optBox == null) {
                     original
                 } else {
                     myUnsealer unseal(optBox)
                 }

Of course the programmer normally wants the language implementation to 
magically take care of many meta-level issues for him.  For example, 
PassByCopy objects should serialize their instance variables by default 
without the programmer needing to say anything further.  Likewise for 
debugging, equality, and auditor verification.  That's why getOptSealedFacet 
should default, not to returning null, but to

             match _ { meta getDefaultOptSealedFacet(self, brand) }

"meta" is a keyword, and provides a syntactic escape for just such purposes. 
  The E BNF just specifies that "meta" may be followed by a message as if you 
were synchronously calling it.  However, the parsing actions dispatch on the 
message name & arity to enforce specific rules and create an expression 
parse node of a specific type.  For getDefaultOptSealedFacet, the first 
"argument" must be the name of a lexically enclosing object definition.

This expression provides the default system-defined meta-level behavior 
for those cases not overridden by the programmer.  This requires the TCB to 
have prior knowledge of certain sealers, which is fine.  The object itself may 
not have access to some of these sealers, such as for equality, in which 
case it can only override requests with those brands to deny service.  
getDefaultOptSealedFacet() itself will default to returning null.



Next: A Pinch of New Syntactic Sugar


Many phone conversations with Dean and MarcS, and memories of Joule's 
"Energetic Secrets", contributed enormously to these ideas.  Thanks!


As always, fire away!



         Cheers,
         --MarkM