Security Demands Simplicity (was: Private Slots)

Mark S. Miller erights at google.com
Thu Jan 17 09:00:43 PST 2013


First, I agree with David that security is important, both for
security per se and for modularity in general. ES5 improved the
language when it fixed the accidental leaks between the world of
objects and scope-chain objects, like thrown functions. ES5/strict
improved the language when it made functions really encapsulated,
poisoned caller and arguments, and repaired the remaining violations
of static scoping. ES6 modules and CommonJS/Node modules were born and
remain encapsulated. I have not heard anyone suggesting any of these
would be improved by introducing pliers for opening them against their
will (except in a debugger, which is a different issue). Tons of code
have been written in ES3. We still made the world a better place when
we plugged its leaks.

I also agree with David that unique symbols are not an encapsulation
mechanism, but rather, merely a mechanism to avoid namespace
collisions.

As for Java's reflective breakage of "private",
<http://www.nbcnews.com/technology/technolog/us-warns-java-software-security-concerns-escalate-1B7938755>.


However, after the "Private Slots" thread, I spent a sleepless night
chewing on getting rid of private symbols. I now think we should.
Going back to my earlier


On Wed, Jan 16, 2013 at 12:37 PM, Mark S. Miller <erights at google.com> wrote:
> My position on private symbols.
>
> My position on classes is and has always been that classes are worth
> introducing into the language *only* if they give us, or can be used
> with, an affordable means for true object encapsulation. Assuming
> Allen is right about what actual implementors will do (which I find
> plausible) then WeakMaps are not that means.

I still have this position on classes. But I no longer buy that
pessimistic conclusion about WeakMaps. Consider how WeakMaps would be
used by the expansion of classes-with-private. Just 'cause it's on the
top of my head, below I use the old representation of one WeakMap per
class providing access to a record of all the private state. For the
same reason, I'll use the encapsulation of the Purse example without
any of the numeric checks.

class Purse {
    constructor(private balance) {
    getBalance() { return balance; }
    makePurse() { return Purse(0); }
    deposit(amount, srcPurse) {
        private(srcPurse).balance -= amount;
        balance += amount;
    }
}

expansion

let Purse = (function() {
    let amp = WeakMap();
    function Purse(balance) {
        amp.set(this, Object.seal({
            get balance() { return balance; },
            set balance(newBalance) { balance = newBalance; }
        }));
    }
    Purse.prototype = {
        getBalance: function() { return balance; },
        makePurse: function() { return Purse(0); },
        deposit: function(amount, srcPurse) {
            amp.get(srcPurse).balance -= amount;
        }
    }
    return Purse;
})();


Ignore the issues about whether we should use one WeakMap per class
and a private record as above, or a WeakMap per private field name
declaration.

Ignore the use of accessors so that private field names track
variables. If we had instead stored the state in the private field and
compiled getBalance to use the field, that doesn't affect the
important issue.



**********************
Notice that the lifetime of amp cannot be shorter than the lifetimes
of the Point instances, since the instances retain amp, and these
instances are the only objects ever stored as keys in amp.
**********************



For this usage common pattern, the lifetime of the keys of amp cannot
outlive the lifetime of amp.

Thus, no matter what the spec says, normatively or not, about expected
storage costs, competitive pressures will drive JS engine implementors
to make this efficient. The way to make this efficient is by the
technique previously discussed -- hang the private state off of the
object, not the weakmap. Use the weakmap only as an unforgeable token
for naming and accessing this state. If we get rid of private symbols,
we should expect this pattern of usage of WeakMap to have the same
cost that private symbols would have had. We can help implementors
achieve that by having this expansion call instead
"WeakMap(USE_KEY_LIFETIME_HINT)" or whatever it is called. Then
implementations would not have to recognize the pattern by other
means.

Complexity is the enemy of security. We already have four
encapsulation mechanisms in ES6 in addition to private symbols:
1) functions encapsulating lexical variables
2) WeakMaps
3) proxies encapsulating handlers and targets
4) modules encapsulating what they don't export.

With enough spec and engineering effort, any of these could be grown
into a means for providing efficient class/object encapsulation. Of
them, I think #2 is most plausible. Even is we find #2 does not work
out, we should think about growing one of the other candidates as an
alternative to private symbols. Security demands simplicity, and
semantic simplicity is more important than implementation simplicity.



--
    Cheers,
    --MarkM


More information about the es-discuss mailing list