Security Demands Simplicity (was: Private Slots)

David Bruant bruant.d at gmail.com
Tue Jan 22 05:28:47 PST 2013


Le 21/01/2013 22:31, Tom Van Cutsem a écrit :
> Let's talk through Allen and Brandon's suggestion of auto-unwrapping 
> private symbol access on proxies.
> If a membrane can intercept all exchanged private symbols I think this 
> could be made to work.
Agreed. Unfortunately, I think the condition ("If a membrane can 
intercept all exchanged private symbols") cannot be fulfilled 
practically. Let's start with:

     var o = {a:{a:{a:new PrivateSymbol()}}}
     // send o across the membrane

The membrane has to traverse o to find the symbol. The membrane "can" do 
it, but it will cost the complete traversal of all objects being passed 
back and forth which has a cost. An arbitrarily big cost for arbitrarily 
complex objects.

I could stop the argument here, but for the fun of it, I'll go further :-)

     function PasswordProtectedSymbol(symbol, password){
         return new Proxy({}, {
             get: function(target, name){
                 if(name === password)
                     return symbol;
             }
         })
     }

If such an object is crosses a membrane, the membrane needs to know the 
password to find the encapsulated symbol. The membrane "can" know the 
password from previous communication between untrusted parties, but it 
requires to know that such string was the password (note that the 
property name is not on the target, so Object.gOPN cannot help the 
membrane). Things can get trickier if the password is partially computed 
in both sides. For the membrane to know the password, it requires the 
membrane author to read and understand the code of both untrusted 
parties in order to understand the semantics of the communication 
between the 2 parties. This is very expensive and error prone.

Let's see another pattern. As an intermediate state in the 
demonstration, consider the following InfiniteObject abstraction:

     var infiniteHandler = {
         get: function(){
             return new InfiniteObject();
         }
     }
     var target = {};

     function InfiniteObject(){
         return new Proxy(target, infiniteHandler)
     }

     var do = new InfiniteObject();
     do.you.think.this.can.ever.end ? 'nope' : 'yep';

A slightly different implementation could accept all 1-char strings (why 
not even putting them in the target) and decide that the proxy-chain 
stops to provide a private symbol if you pass in a password as in 
"obj.p.a.s.s.w.o.r.d". In this case, fully traversing the object means 
doing an infinite loop and the above point about passwords and membrane 
being aware of communication semantics still stands.

The membrane can always capture the private symbols I think, but it may 
come at an unpractical price.


hmm... For all the above tricks to work, it requires that the untrusted 
parties can create their own functions that the membrane is unaware of. 
Maybe it could be considered to add a Loader option so that syntax-based 
initializations ({}, [], function(){}...) trigger a custom constructor 
in loaded code (only in loaded code, not globally of course). This 
custom constructor would make *all* new objects

If loaders don't have such an option, it's possible to parse the code 
and wrap all initializations. I have written such a tool recently for a 
completely unrelated purpose [1]. It's a ugly AST hack, please forgive 
the naivety of the implementation. Also the translated code does not 
have exactly the same semantics than the source one for hoisting reasons 
and a couple of other edge cases but I chose not to care in my case as a 
first approximation.

I have no idea if the perf cost would be more practical for either the 
loader or the rewriting solution. It seems worth investigating though.

David

[1] 
https://github.com/DavidBruant/HarmonyProxyLab/blob/ES3AndProxy/ES3AndProxy/tests/tools/Test262Converter/transform.js


More information about the es-discuss mailing list