Lecture series on SES and capability-based security by Mark Miller

# Tom Van Cutsem (12 years ago)

Last month, Mark Miller gave a series of talks at the University of Brussels on SES and capability-based security. The videos of the talks have now been made fully available on Youtube. I think this may be of interest to some on this list. As many of you know, Mark uses ECMAScript 5 as a foundation for his Secure ECMAScript (SES) work. Also, I heartily recommend the talks to those not entirely convinced of the necessity of private/const/frozen features. Mark makes a pretty good case for encapsulation as a necessary building block for ocap-based security.

Abstract and links to videos below.

Cheers, Tom

Talk 1/2: Secure Distributed Programming with Object-capabilities in JavaScript

Until now, browser-based security has been hell. The object-capability (ocap) model provides a simple and expressive alternative. Google's Caja project uses the latest JavaScript standard, EcmaScript 5, to support fine-grained safe mobile code, solving the secure mashup problem. Dr. SES -- Distributed Resilient Secure EcmaScript -- extends the ocap model cryptographically over the network, enabling RESTful composition of mutually suspicious web services. We show how to apply the expressiveness of object programming to the expression of security patterns, solving security problems normally thought to be difficult with simple elegant programs.

Slides: soft.vub.ac.be/events/mobicrant_talks/talk1_ocaps_js.pdf

Video: www.youtube.com/watch?v=w9hHHvhZ_HY

Talk 2/2: Bringing Object-orientation to Security Programming

Just as we should not expect our base programming language to provide all the data types we need, so we should not expect our security foundation to provide all the abstractions we need to express security policy. The answer to both is the same: We need foundations that provide simple abstraction mechanisms, which we use to build an open ended set of abstractions, which we then use to express policy. We show how to use EcmaScript 5 to enforce the security latent in object-oriented abstraction mechanisms: encapsulation, message-passing, polymorphism, and interposition. With these secured, we show how to build abstractions for confinement, rights amplification, transitive wrapping and revocation, and smart contracts.

Slides: soft.vub.ac.be/events/mobicrant_talks/talk2_OO_security.pdf

Video: www.youtube.com/watch?v=oBqeDYETXME

# John J Barton (12 years ago)

On Thu, Nov 3, 2011 at 12:52 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

. Also, I heartily recommend the talks to those not entirely convinced of the necessity of private/const/frozen features. Mark makes a pretty good case for encapsulation as a necessary building block for ocap-based security

If I understood Mark correctly, the features needed for SES are already part of ES.5 and are shipping in browsers (and hence don't bear upon future features). Did I misunderstand?

jjb

# Mark S. Miller (12 years ago)

On Thu, Nov 3, 2011 at 2:10 PM, John J Barton <johnjbarton at johnjbarton.com>wrote:

On Thu, Nov 3, 2011 at 12:52 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

. Also, I heartily recommend the talks to those not entirely convinced of the necessity of private/const/frozen features. Mark makes a pretty good case for encapsulation as a necessary building block for ocap-based security

If I understood Mark correctly, the features needed for SES are already part of ES.5 and are shipping in browsers

That is correct. Any browser on which < es-lab.googlecode.com/svn/trunk/src/ses/explicit.html> passes, which

includes Chrome 16, FF Nightly 10, and WebKit Nightly r98831. Opera 12 alpha build 1116 and IE 10 preview 2 both fall short by only one bug that both teams are aware of and that test262 tests for, so I expect/hope the release of both will also be adequate.

(and hence don't bear upon future features). Did I misunderstand?

These do bear on future features in three ways:

  1. Future features could easily destroy all the security gains that ES5 achieved. As an example, for a long time it wasn't clear -- to me at least -- how "freeze" should affect the ability to mutate hidden own properties, i.e., own properties named by "private names". The two obvious answers are a) hidden own properties are treated just like normal own properties -- "freeze" makes them non-configurable and (if data) non-writable b) hidden own properties are completely exempt from the constraints imposed by "freeze". However, both of these answers would decrease rather than increase security. Further, #b introduces a capability leak, and so would be fatal for SES. The answer we settled on at the last meeting is c) hidden own properties that already exist are exempt from "freeze" and "seal" constraints. However, once an object is non-extensible (whether by "freeze", "seal", or "preventExtensions"), then new hidden own properties cannot be added to it.

  2. The notorious Ch16 exemptions as stated in ES5.1 < es5.github.com/#x16>, renders all security reasoning necessarily

unsound. For example, an implementation that introduced a non-configurable ambient method for deleting all the user's files would still be considered conformant. Going forward, we should make at least < conventions:make_non-standard_properties_configurable>

normative. Beyond this, we need to understand what is the least additional Ch16 reform needed to enable security reasoning to be sound.

  1. Although SES is formally an object-capability language, i.e., it has all the formal properties required by the object-capability model, it has bad usability properties for writing defensive abstractions, and therefore bad usability properties for use as an object-capability language or for serious software engineering. One example:

In a SES environment, or, for present purposes, an ES5/strict environment in which all primordial built-in objects are transitively frozen, say Alice uses the following abstraction:

function makeTable() {
  var array = [];
  return Object.freeze({
    add: function(v) { array.push(v); },
    store: function(i, v) { array[i] = v; },
    get: function(i) { return array[i]; }
  });
}

Say she uses it to make a "table" instance with three methods: add, store, and get. She gives this instance to Bob. Alice and Bob are mutually suspicious. All of us as programmers, looking at this code, can tell that Alice intended the table abstraction to encapsulate the array. Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

If you've already seen this puzzle and know the answer, please don't post. If no one else has posted the correct answer in 24 hours, I will.

Note that I don't see any realistic way to fix problem #3 in the ES.next language. My point is only that defensive programming is tricky even after you've gotten all the formal properties you need. As ES.next introduces various new abstraction mechanisms, whether classes, enhanced object literals, proxies, modules, or private names, the design of these can either help or hurt those attempting to write defensive abstractions. Any class abstraction that is only useful for making indefensible instances is worse than useless -- it is actively harmful, both to security and to serious software engineering.

# Douglas Crockford (12 years ago)

On 11:59 AM, Mark S. Miller wrote:

Note that I don't see any realistic way to fix problem #3 in the ES.next language. My point is only that defensive programming is tricky even after you've gotten all the formal properties you need. As ES.next introduces various new abstraction mechanisms, whether classes, enhanced object literals, proxies, modules, or private names, the design of these can either help or hurt those attempting to write defensive abstractions. Any class abstraction that is only useful for making indefensible instances is worse than useless -- it is actively harmful, both to security and to serious software engineering.

I share your concern about the unintended consequences of new features. The dangers are very real, and the patterns can be extremely subtle.

But while #3 is not fixable, there are features of ES5 that make it easier to live with. For example, if you had built makeTable with

 var array = Object.create(null),
     length = 0;

and adapted the methods accordingly, then in this example, the leakage could have been avoided without excessive trickiness. I think this level of practice is teachable.

# Claus Reinke (12 years ago)

In .. an ES5/strict environment in which all primordial built-in objects are transitively frozen, ..

function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

.. Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

Hm. A favorite pattern that I haven't thought about enough, it seems. My curiosity did cost me some sleep:-| My favorite work around for your constraints is this little shim:-)

// dynamic language Object.freeze = function(obj){return obj};

You imply that this is not intended, so I can show it without spoiling the fun. I was surprised that this works.

The first solution that came to mind ought to be defeated by your "transitively frozen" constraint, and it is, in current JS implementations, though not all JS implementations in the wild are there yet - do you "feature-detect" old engines and issue warnings if they invalidate your base assumptions?

The second approach took longer to find but relies on non-standard features (and again, I suspect a bug/interpretation issue).

Both ideas are weak, in that they could be blocked by type checks.

If you've already seen this puzzle and know the answer, please don't post. If no one else has posted the correct answer in 24 hours, I will.

Neither of my approaches seems to be the droid you are looking for, given your "no realistic fix" remark, so I'm curious what else I've missed.

Note that I don't see any realistic way to fix problem #3 in the ES.next language. My point is only that defensive programming is tricky even after you've gotten all the formal properties you need. As ES.next introduces various new abstraction mechanisms, whether classes, enhanced object literals, proxies, modules, or private names, the design of these can either help or hurt those attempting to write defensive abstractions. Any class abstraction that is only useful for making indefensible instances is worse than useless -- it is actively harmful, both to security and to serious software engineering.

You also rely on you security base framework being the first to run, and on nobody trying to modify source on load, right?

Claus clausreinke.github.com, clausreinke.github.com/js-tools

# Juan Ignacio Dopazo (12 years ago)

On Thu, Nov 3, 2011 at 7:55 PM, Mark S. Miller <erights at google.com> wrote:

 function makeTable() {
  var array = [];
  return Object.freeze({
    add: function(v) { array.push(v); },
    store: function(i, v) { array[i] = v; },
    get: function(i) { return array[i]; }
  });
}

Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

So Bob can cheat by extending Array.prototype, right?

Object.defineProperty(Array.prototype, 'self', { get: function() { return this; } }); console.log(table.get('self'));

That's why Douglas proposes using var array = Object.create(null), an object without a prototype that can be extended.

Juan

# Allen Wirfs-Brock (12 years ago)

On Nov 4, 2011, at 8:50 AM, Juan Ignacio Dopazo wrote:

On Thu, Nov 3, 2011 at 7:55 PM, Mark S. Miller <erights at google.com> wrote: function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

So Bob can cheat by extending Array.prototype, right?

MarkM said "all primordial built-in objects are transitively frozen" so Array.prototype is not extensible and Array.prototype.push is not writable...

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 8:14 AM, Claus Reinke <claus.reinke at talk21.com>wrote:

Hm. A favorite pattern that I haven't thought about enough, it seems. My curiosity did cost me some sleep:-| My favorite work around for your constraints is this little shim:-)

// dynamic language Object.freeze = function(obj){return obj};

You imply that this is not intended, so I can show it without spoiling the fun. I was surprised that this works.

Since the primordials are already frozen, this assignment fails.

The first solution that came to mind ought to be defeated by your "transitively frozen" constraint, and it is, in current JS implementations, though not all JS implementations in the wild are there yet - do you "feature-detect" old engines and issue warnings if they invalidate your base assumptions?

Yes, as shown by < es-lab.googlecode.com/svn/trunk/src/ses/explicit.html>.

For example, Firefox 7.0.1 shows "Max Severity: Not isolated(4) is not SES-safe." Chrome 16 shows "Max Severity: Safe spec violation(1)." Firefox Nightly 10.0a1 shows no Max Severity line because it encountered no unrepairable problems.

If you do a view source, you'll see the text

// This severity is too high for any use other than development.
ses.maxAcceptableSeverityName = 'NEW_SYMPTOM';

For production use, depending on you're purpose, you'd probably let the max acceptable severity default, in which case initSES.js will fail quickly as soon as it detects that this platform cannot be made SES-safe. Once initSES.js terminates, ses.ok() indicates whether the max acceptable severity has been exceeded. The severity levels can currently be found at < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/repairES5.js#85

We are currently in the process of integrating SES in with the rest of Caja. If you're only targeting SES-safe browsers you can still use initSES.js by itself. Otherwise, if you deploy through Caja, the plan is that Caja will detect whether this is a SES-safe platform, deploy without translation using SES if so, and otherwise fall back to Caja's ES5-to-ES3 translator.

The second approach took longer to find but relies on non-standard features (and again, I suspect a bug/interpretation issue).

What is your second approach?

Both ideas are weak, in that they could be blocked by type checks.

How?

If you've already seen this puzzle and know the answer, please don't

post. If no one else has posted the correct answer in 24 hours, I will.

Neither of my approaches seems to be the droid you are looking for, given your "no realistic fix" remark, so I'm curious what else I've missed.

I should take this opportunity to reveal that David Herman found a bug last night with my challenge. Due to a "feature" of ES3 which ES5.1 preserves, and which I keep forgetting about because it has always seemed only to be annoyance, the attack I had in mind actually fails on an ES5.1 conformant browser. Another reason I missed it is my attack succeeds on Chrome/v8, because it does not implement this "feature". Dave found it because FF/SpiderMonkey is ES5.1 conformant in this regard. After he rediscovered the attack, it failed when he tried it on SpiderMonkey, reminding both of us of this annoyance. Perhaps this annoyance really is a feature after all?

I asked him to keep quiet about it for the remainder of the 24 hours because I was curious to see what people came up with.

Congrats to Dave!

Note that I don't see any realistic way to fix problem #3 in the ES.next

language. My point is only that defensive programming is tricky even after you've gotten all the formal properties you need. As ES.next introduces various new abstraction mechanisms, whether classes, enhanced object literals, proxies, modules, or private names, the design of these can either help or hurt those attempting to write defensive abstractions. Any class abstraction that is only useful for making indefensible instances is worse than useless -- it is actively harmful, both to security and to serious software engineering.

You also rely on you security base framework being the first to run,

Yes, absolutely.

and on nobody trying to modify source on load, right?

I think the answer to this is "yes" as well, but first I should ask for clarification: source to what?

# Axel Rauschmayer (12 years ago)

[edited and sent to es-discuss, just in case]

function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

[...] Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

How about:

function Bob(t) {
   var stolenArray;
   var hackedPush = function() {
       stolenArray = this;
   };
   t.store("push", hackedPush);
   t.add(0);
   console.log(stolenArray);
}
Bob(makeTable());
# Axel Rauschmayer (12 years ago)

function Bob(t) { var stolenArray; var hackedPush = function() { stolenArray = this; }; t.store("push", hackedPush); t.add(0); console.log(stolenArray); } Bob(makeTable());

As an aside: This problem would go away if we really did distinguish between accessing a property and accessing a collection element. Then the former would be done via Object.* methods, while the latter would be done via square brackets.

# Jorge (12 years ago)

On 03/11/2011, at 23:55, Mark S. Miller wrote:

  1. Although SES is formally an object-capability language, i.e., it has all the formal properties required by the object-capability model, it has bad usability properties for writing defensive abstractions, and therefore bad usability properties for use as an object-capability language or for serious software engineering. One example:

In a SES environment, or, for present purposes, an ES5/strict environment in which all primordial built-in objects are transitively frozen, say Alice uses the following abstraction:

function makeTable() {
  var array = [];
  return Object.freeze({
    add: function(v) { array.push(v); },
    store: function(i, v) { array[i] = v; },
    get: function(i) { return array[i]; }
  });
}

Say she uses it to make a "table" instance with three methods: add, store, and get. She gives this instance to Bob. Alice and Bob are mutually suspicious. All of us as programmers, looking at this code, can tell that Alice intended the table abstraction to encapsulate the array. Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

Yes, this:

function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

o= makeTable(); o.add(1); o.add(2); o.add(3); o.add('Yay!');

o.store('proto', {push:function () { console.log(this) }}); o.add();

Gives:

[ 1, 2, 3, 'Yay!' ]

# Allen Wirfs-Brock (12 years ago)

On Nov 4, 2011, at 10:33 AM, Axel Rauschmayer wrote:

How about:

function Bob(t) { var stolenArray; var hackedPush = function() { stolenArray = this; }; t.store("push", hackedPush);

If Array.prototype has been frozen (as the problem statement implied) then the above line should throw.

# Jorge (12 years ago)

On 04/11/2011, at 18:51, Jorge wrote:

On 03/11/2011, at 23:55, Mark S. Miller wrote:

  1. Although SES is formally an object-capability language, i.e., it has all the formal properties required by the object-capability model, it has bad usability properties for writing defensive abstractions, and therefore bad usability properties for use as an object-capability language or for serious software engineering. One example:

In a SES environment, or, for present purposes, an ES5/strict environment in which all primordial built-in objects are transitively frozen, say Alice uses the following abstraction:

function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

Say she uses it to make a "table" instance with three methods: add, store, and get. She gives this instance to Bob. Alice and Bob are mutually suspicious. All of us as programmers, looking at this code, can tell that Alice intended the table abstraction to encapsulate the array. Given just a table instance, can Bob nevertheless obtain direct access to the underlying array?

Yes, this:

function makeTable() { var array = []; return Object.freeze({ add: function(v) { array.push(v); }, store: function(i, v) { array[i] = v; }, get: function(i) { return array[i]; } }); }

o= makeTable(); o.add(1); o.add(2); o.add(3); o.add('Yay!');

o.store('proto', {push:function () { console.log(this) }});

Or even easier yet, what Axel says:

o.store('push', function () { console.log(this) });

o.add();

Gives:

[ 1, 2, 3, 'Yay!' ]

[ 1, 2, 3, 'Yay!', push: [Function] ]

# Axel Rauschmayer (12 years ago)

But hackedPush is added to the instance, not Array.prototype.

# Allen Wirfs-Brock (12 years ago)

You can't "over-ride" an inherited read-only property by assignment. See ES5.1 8.12.4

You could do it via Object.defineProperty, but that requires direct access to the object.

# David Herman (12 years ago)

This is the only one I've seen that seems like it should work, but it depends on whether SES/Caja/etc have some sort of way of neutering proto. Just from hacking around, I don't see much way of censoring it in SpiderMonkey.

MarkM, do you have any tricks for censoring proto?

# Axel Rauschmayer (12 years ago)

Cool, didn’t know.

# Claus Reinke (12 years ago)

// dynamic language Object.freeze = function(obj){return obj};

You imply that this is not intended, so I can show it without spoiling the fun. I was surprised that this works.

Since the primordials are already frozen, this assignment fails.

Yes. It just re-emphasizes the need to be the first to run. Needing to freeze "freeze" seemed a fun way to show the Muenchhausen aspect of getting a secure environment.

I was surprised that several of my current JS environments do not yet implement freeze, btw. Need to watch that.

The first solution that came to mind ought to be defeated by your "transitively frozen" constraint, and it is, in current JS implementations, though not all JS implementations in the wild are there yet - do you "feature-detect" old engines and issue warnings if they invalidate your base assumptions?

Yes, as shown by < es-lab.googlecode.com/svn/trunk/src/ses/explicit.html>. For example, Firefox 7.0.1 shows "Max Severity: Not isolated(4) is not SES-safe." Chrome 16 shows "Max Severity: Safe spec violation(1)." Firefox Nightly 10.0a1 shows no Max Severity line because it encountered no unrepairable problems.

If you do a view source, you'll see the text

// This severity is too high for any use other than development. ses.maxAcceptableSeverityName = 'NEW_SYMPTOM';

For production use, depending on you're purpose, you'd probably let the max acceptable severity default, in which case initSES.js will fail quickly as soon as it detects that this platform cannot be made SES-safe. Once initSES.js terminates, ses.ok() indicates whether the max acceptable severity has been exceeded. The severity levels can currently be found at < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/repairES5.js#85

We are currently in the process of integrating SES in with the rest of Caja. If you're only targeting SES-safe browsers you can still use initSES.js by itself. Otherwise, if you deploy through Caja, the plan is that Caja will detect whether this is a SES-safe platform, deploy without translation using SES if so, and otherwise fall back to Caja's ES5-to-ES3 translator.

Is this feature-testing something that could be emphasized more in SES presentations? It might be useful to remind JS coders of the difficulties of security - it isn't just PHP, and just because there are efforts like AdSafe or SES, that doesn't mean that the fight is won, or that solutions are easy. Highlighting the difficulties could avoid the I-feel-safe-because-there-are-magic-security-black-boxes.

The second approach took longer to find but relies on non-standard features (and again, I suspect a bug/interpretation issue).

What is your second approach?

Is the time up, already?-)

My first approach was using store to modify array.push, also suggested by others. That should fail, because Array.prototype is frozen and freezing prevents shadowing inherited properties.

In FireFox 7, it fails, but the older node-0.4.9 doesn't seem to care..

My second approach was to use store to modify array.proto, adding a rogue push to a new non-frozen prototype. That should fail if Object.prototype is frozen, for the same shadowing reason.

In FireFox 7, it fails, but the older node-0.4.9 doesn't seem to care..

To guard against, one might prevent proto use in general, as AdSafe does, I think). Or ensure that array.push is the method one wants by not getting it from modifiable array.

Perhaps proto should not be writeable in "use strict"?

Perhaps test262 failures that enable security issues should be highlighted separately, to encourage upgrading? Or, if test262 cannot test for non-standard features, the SES initialization checks could be given a similarly prominent (publicity-worthy) status as test262?

Both ideas are weak, in that they could be blocked by type checks.

How?

Ah, yes. I was thinking that checking the type of store's index parameter would prevent both of these attempts. But there is nothing in your spec that rules out string parameters. So, one would have to resort to blacklisting :-(, starting with..

(!i in array||array.hasOwnProperty(i))

If you've already seen this puzzle and know the answer, please don't

post. If no one else has posted the correct answer in 24 hours, I will.

Neither of my approaches seems to be the droid you are looking for, given your "no realistic fix" remark, so I'm curious what else I've missed.

I should take this opportunity to reveal that David Herman found a bug last night with my challenge. Due to a "feature" of ES3 which ES5.1 preserves, and which I keep forgetting about because it has always seemed only to be annoyance, the attack I had in mind actually fails on an ES5.1 conformant browser. Another reason I missed it is my attack succeeds on Chrome/v8, because it does not implement this "feature". Dave found it because FF/SpiderMonkey is ES5.1 conformant in this regard. After he rediscovered the attack, it failed when he tried it on SpiderMonkey, reminding both of us of this annoyance. Perhaps this annoyance really is a feature after all?

I asked him to keep quiet about it for the remainder of the 24 hours because I was curious to see what people came up with.

Congrats to Dave!

pity.. still, it was an interesting exercise.

You also rely on you security base framework being the first to run,

Yes, absolutely.

and on nobody trying to modify source on load, right?

I think the answer to this is "yes" as well, but first I should ask for clarification: source to what?

The source being run/being "attacked". Re-directing the loader to rewrite the source could strip away "security" features even easier than overwriting non-frozen standard objects.

Claus clausreinke.github.com, clausreinke.github.com/js-tools

# Brendan Eich (12 years ago)

On Nov 4, 2011, at 9:39 AM, Mark S. Miller wrote:

Perhaps this annoyance really is a feature after all?

It is!

I asked him to keep quiet about it for the remainder of the 24 hours because I was curious to see what people came up with.

Congrats to Dave!

Kris Zyp tweeted his attack and must have been testing only in Chrome. I tweeted back:

twitter.com/#!/kriszyp/status/132231939569618944, twitter.com/#!/BrendanEich/status/132367206657957888

kriszyp kriszyp An answer to M Miller's es-discuss challenge (w/out spoiling on ml): var array;table.store("push", function(){array = this;});table.add(); 22 hours ago Favorite Retweet Reply in reply to ↑

@BrendanEich BrendanEich @kriszyp should throw when store("push", ...) tries to shadow frozen Array.prototype.push -- does in SpiderMonkey. cc: @awbjs

Note that I don't see any realistic way to fix problem #3 in the ES.next language.

If you mean the problem that o[x] = y can shadow a writable proto-method, that's a feature too. But freezing helps.

If you could redefine [] as an operator on all objects, perhaps that would help. Or hurt. Both, probably. That isn't what Allen proposes, though. It would have to be universal AFAICT. Thoughts?

# Allen Wirfs-Brock (12 years ago)

On Nov 4, 2011, at 2:33 PM, Brendan Eich wrote:

... If you could redefine [] as an operator on all objects, perhaps that would help. Or hurt. Both, probably. That isn't what Allen proposes, though. It would have to be universal AFAICT. Thoughts?

Actually, that sounds pretty much what I proposed. My proposal was that [ ] (actually RHS and LHS independently) would invoke a method using a well known property name if it was present on the object to the left of the [ ]. If the property was not present (own or inherited) then it would fall back to current behavior.

# Brendan Eich (12 years ago)

On Nov 4, 2011, at 2:40 PM, Allen Wirfs-Brock wrote:

On Nov 4, 2011, at 2:33 PM, Brendan Eich wrote:

... If you could redefine [] as an operator on all objects, perhaps that would help. Or hurt. Both, probably. That isn't what Allen proposes, though. It would have to be universal AFAICT. Thoughts?

Actually, that sounds pretty much what I proposed. My proposal was that [ ] (actually RHS and LHS independently) would invoke a method using a well known property name if it was present on the object to the left of the [ ]. If the property was not present (own or inherited) then it would fall back to current behavior.

So Object.prototype customization would cover all cases? Except Proxies, of course -- and host objects.

# Allen Wirfs-Brock (12 years ago)

On Nov 4, 2011, at 2:49 PM, Brendan Eich wrote:

On Nov 4, 2011, at 2:40 PM, Allen Wirfs-Brock wrote:

On Nov 4, 2011, at 2:33 PM, Brendan Eich wrote:

... If you could redefine [] as an operator on all objects, perhaps that would help. Or hurt. Both, probably. That isn't what Allen proposes, though. It would have to be universal AFAICT. Thoughts?

Actually, that sounds pretty much what I proposed. My proposal was that [ ] (actually RHS and LHS independently) would invoke a method using a well known property name if it was present on the object to the left of the [ ]. If the property was not present (own or inherited) then it would fall back to current behavior.

So Object.prototype customization would cover all cases? Except Proxies, of course -- and host objects.

Yes, except that what you would expect to put into Object.prototype would actually (or also) be defined as default behavior in order to ensure that that Object.create(null) objects, etc continue to have ES1-5 behavior. Because collection behavior is defined via method invocation, proxies don't need to do/have anything special (although a Proxy's [[Get]] handler) could look for accesses to the special [ ] behavior methods. Same for host objects, except who knows what a host object really is...

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 10:33 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

How about:

function Bob(t) { var stolenArray; var hackedPush = function() { stolenArray = this; }; t.store("push", hackedPush); t.add(0); console.log(stolenArray); } Bob(makeTable());

Yes, that is precisely the attack I had in mind. Congrats!

As Dave Herman discovered, it works on v8 but not on SpiderMonkey due to a known bug in v8 that I had forgotten was a bug. According to the ES5.1 spec, you can't override a non-writable data property with a simple assignment. I had always considered this an unfortunate annoyance and irrelevant to security, but in this case it did happen to accidentally prevent an attack.

# Axel Rauschmayer (12 years ago)

So Object.prototype customization would cover all cases? Except Proxies, of course -- and host objects.

Yes, except that what you would expect to put into Object.prototype would actually (or also) be defined as default behavior in order to ensure that that Object.create(null) objects, etc continue to have ES1-5 behavior. Because collection behavior is defined via method invocation, proxies don't need to do/have anything special (although a Proxy's [[Get]] handler) could look for accesses to the special [ ] behavior methods. Same for host objects, except who knows what a host object really is...

Have you come to a decision with regard to a separate .[] operator for collections (including arrays)?

I think it’s better to rededicate []:

  • .[] would invalidate all existing array code.
  • [] would only invalidate code that uses computed property names. And that can be fixed via the default behavior.

It might make sense to have a "stricter mode" that turns off the default behavior of []:

  • It would thus force you to use methods such as (the yet to be defined) Object.setOwnProperty() and Object.getProperty() when you want to compute the name of a property.
  • Stricter mode could also restrict [] for arrays to just numbers, including negative numbers for accessing elements relative to the end of an array.
# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 10:37 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

function Bob(t) { var stolenArray; var hackedPush = function() { stolenArray = this; }; t.store("push", hackedPush); t.add(0); console.log(stolenArray); } Bob(makeTable());

As an aside: This problem would go away if we really did distinguish between accessing a property and accessing a collection element. Then the former would be done via Object.* methods, while the latter would be done via square brackets.

I admit that I haven't followed the previous thread on ".[" and such. Is there a short summary? I ask because my diagnosis is similar but my conclusion is reversed. The lesson I take from this is not to use objects as maps. It you want a map, create a Map() and say map.get(key) and map.set(key, value) rather than using square brackets.

The remaining case is arrays. My conclusion there is to always say array[+i] rather than array[i]. If Alice had already been practicing this as a habit, then her table abstraction would have been naturally robust against this attack even if Alice never thinks of this specific attack.

Having eliminated both of these uses of unmarked square brackets (lists and maps), the only remaining justified use of unmarked square bracket indexing is reflection. If you see an unmarked square bracket that's not doing reflection, you should probably refactor.

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 10:51 AM, Jorge <jorge at jorgechamorro.com> wrote:

o= makeTable(); o.add(1); o.add(2); o.add(3); o.add('Yay!');

o.store('proto', {push:function () { console.log(this) }}); o.add();

Gives:

[ 1, 2, 3, 'Yay!' ]

Very nice! Your use of proto is very clever, and should work on SpiderMonkey, or any other conforming browser that also supports de-facto proto.

Note that the mitigating practice I just recommended: always saying array[+i] rather than array[i], would have prevented this attack as well, even though I had not thought of it before.

# Axel Rauschmayer (12 years ago)

As an aside: This problem would go away if we really did distinguish between accessing a property and accessing a collection element. Then the former would be done via Object.* methods, while the latter would be done via square brackets.

I admit that I haven't followed the previous thread on ".[" and such. Is there a short summary? I ask because my diagnosis is similar but my conclusion is reversed. The lesson I take from this is not to use objects as maps. It you want a map, create a Map() and say map.get(key) and map.set(key, value) rather than using square brackets.

The remaining case is arrays. My conclusion there is to always say array[+i] rather than array[i]. If Alice had already been practicing this as a habit, then her table abstraction would have been naturally robust against this attack even if Alice never thinks of this specific attack.

Having eliminated both of these uses of unmarked square brackets (lists and maps), the only remaining justified use of unmarked square bracket indexing is reflection. If you see an unmarked square bracket that's not doing reflection, you should probably refactor.

I agree completely (see also my other email):

  • Never use objects as maps.
  • Introduce collection classes.
  • Try to make arrays fit into the collection framework.
# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 11:22 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

You can't "over-ride" an inherited read-only property by assignment. See ES5.1 8.12.4

You could do it via Object.defineProperty, but that requires direct access to the object.

Exactly. Bob has no way to trick Alice's table abstraction to do a defineProperty.

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 11:46 AM, David Herman <dherman at mozilla.com> wrote:

This is the only one I've seen that seems like it should work, but it depends on whether SES/Caja/etc have some sort of way of neutering proto. Just from hacking around, I don't see much way of censoring it in SpiderMonkey.

MarkM, do you have any tricks for censoring proto?

Not censoring, no. However, for code that follows the following best practices, uncensored proto should be harmless.

  1. Avoid objects as maps -- use Maps as maps.
  2. Always use array[+i] for numeric indexing. Note that this can still access properties named 'NaN', 'Infinity', and '-Infinity'.
  3. Always freeze, or at least seal, objects potentially exposed directly to untrusted clients.

.#3 is crucial, and works because ES5.1 section 8.6.2 says: "if [[Extensible]] is false the value of the [[Class]] and [[Prototype]] internal properties of the object may not be modified. Once the value of an [[Extensible]] internal property has been set to false it may not be subsequently changed to true."

This is tested by test262 at < hg.ecmascript.org/tests/test262/file/c84161250e66/test/suite/ch08/8.6/8.6.2/S8.6.2_A8.js

.

The visible development tip of all major browsers except Opera 12 alpha build 1116 already implement this restriction. initSES.js considers that Opera unsafe for this reason.

Nevertheless, if I could cheaply censor proto I would. Since I'm not, I am very curious is this leaves open other attacks against code obeying the above best practices. If any of you see some other avenue of attack, please let me know. Thanks.

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 12:11 PM, Claus Reinke <claus.reinke at talk21.com>wrote:

// dynamic language

Object.freeze = function(obj){return obj};

You imply that this is not intended, so I can show it without spoiling the fun. I was surprised that this works.

Since the primordials are already frozen, this assignment fails.

Yes. It just re-emphasizes the need to be the first to run. Needing to freeze "freeze" seemed a fun way to show the Muenchhausen aspect of getting a secure environment.

I haven't yet seen the movie. But since it is directed by Terry Gilliam, it's on my list ;).

I was surprised that several of my current JS environments do not yet implement freeze, btw. Need to watch that.

[...]

Is this feature-testing something that could be emphasized more in SES presentations?

I haven't, but I agree with you that I should. I do emphasize that SES only works on ES5, and that on a pre-ES5 browser, we still need the Caja ES5-to-ES3 translator. But I should make clearer how picky initSES.js is about what it considers and ES5 browser.

It might be useful to remind JS coders of the difficulties of security - it isn't just PHP, and just because there are efforts like AdSafe or SES, that doesn't mean that the fight is won, or that solutions are easy. Highlighting the difficulties could avoid the I-feel-safe-because-there-are-**magic-security-black-boxes.

Although I may be understating the difficulties, I think you are overstating them here. This problem is transient. The visible development tip versions of all major browsers are either already SES-safe of fall short by one bug, tested by test262, that I expect both teams to fix. This means that shortly, the most recent released version of all major browsers will be SES-safe. Browser makers have generally been getting more aggressive about encouraging upgrades, so that the tail of old browsers fades out quicker.

I'll go ahead and make a prediction that a year from now the straggler we're all be cursing will be IE8 on Windows-XP, since I do not expect MS to ever backport IE10 to XP, and I expect there will still be Windows users holding onto XP for dear life.

The second approach took longer to find but relies on non-standard

features (and again, I suspect a bug/interpretation issue).

What is your second approach?

Is the time up, already?-)

My first approach was using store to modify array.push, also suggested by others. That should fail, because Array.prototype is frozen and freezing prevents shadowing inherited properties.

In FireFox 7, it fails, but the older node-0.4.9 doesn't seem to care..

My second approach was to use store to modify array.proto, adding a rogue push to a new non-frozen prototype. That should fail if Object.prototype is frozen, for the same shadowing reason.

That is very interesting. I would have expected proto to be exempt from this restriction since it isn't really a property. I'm glad to hear that it is not exempt, at least on SpiderMonkey.

In FireFox 7, it fails, but the older node-0.4.9 doesn't seem to care..

To guard against, one might prevent proto use in general, as AdSafe does, I think).

ADsafe guards against this at the price of including a full accurate JS lexer and parser. One of my goals for SES is to see if we could get to full ocap security without needing an accurate lexer and parser, in order to avoid download bloat. It seems we can.

Or ensure that array.push is the method one wants by not getting it from modifiable array.

For normal defensive SES programming use, I fear this is a bridge too far. For programmers coming from JS, I want defensive SES to still be easy to learn and pleasant to use.

Of course, if Alice notices the danger for this specific example, should could reasonably use it here but not in general. But the problem is the "if Alice notices" part. Secure programming practices only work if they defend against most attacks without the programmer needing to have thought of these attacks.

Perhaps proto should not be writeable in "use strict"?

That's a great idea! This never occurred to me, and I have not heard anyone suggest this. Thanks!

Perhaps test262 failures that enable security issues should be highlighted separately, to encourage upgrading? Or, if test262 cannot test for non-standard features, the SES initialization checks could be given a similarly prominent (publicity-worthy) status as test262?

SES has no normative status yet in TC39, so doing so at test262.ecmascript.org is a bit tricky. At some point I intend to bring SES to TC39, probably on a separate track in the way i18n is separately tracked. Once SES is on its way towards de jure standardization, then such prominence should naturally follow. Thanks for the suggestion.

Of course, nothing stops anyone from publicizing SES in other venues, so please feel free ;).

Both ideas are weak, in that they could be blocked by type checks.

How?

Ah, yes. I was thinking that checking the type of store's index parameter would prevent both of these attempts. But there is nothing in your spec that rules out string parameters. So, one would have to resort to blacklisting :-(, starting with..

I see. My solution, of teaching programmers to write either map.get(key) or array[+i], is really along the same lines. Cool.

pity.. still, it was an interesting exercise.

Glad you enjoyed it.

You also rely on you security base framework being the first to run,

Yes, absolutely.

and on nobody trying to modify source on load, right?

I think the answer to this is "yes" as well, but first I should ask for clarification: source to what?

The source being run/being "attacked". Re-directing the loader to rewrite the source could strip away "security" features even easier than overwriting non-frozen standard objects.

Yes.

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 6:50 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

I agree completely (see also my other email):

  • Never use objects as maps.
  • Introduce collection classes.
  • Try to make arrays fit into the collection framework.

Great! But could you please post a pointer to that other email, or post a summary? Thanks.

# Mark S. Miller (12 years ago)

On Fri, Nov 4, 2011 at 2:33 PM, Brendan Eich <brendan at mozilla.com> wrote:

If you could redefine [] as an operator on all objects, perhaps that would help. Or hurt. Both, probably. That isn't what Allen proposes, though. It would have to be universal AFAICT. Thoughts?

Is there a previous email or a short summary of what "redefine [] as an operator" would entail? I'm sorry that I haven't really followed that thread, but I haven't. Thanks.

# Axel Rauschmayer (12 years ago)

I agree completely (see also my other email):

  • Never use objects as maps.
  • Introduce collection classes.
  • Try to make arrays fit into the collection framework.

Great! But could you please post a pointer to that other email, or post a summary? Thanks.

This is the original thread: www.mail-archive.com/[email protected]/msg10464.html

My understanding (using Allen’s terminology) is as follows (consult Allen’s email that started the thread for further details and a longer rationale).

We have to separate the data domain from the program domain:

  • Arrays: [] is used to access both collection/array elements (data domain) and properties (program domain).
  • Objects: [] is used mainly for properties, but also for collection element access when (ab)using objects as maps from strings to values.

Solutions:

  1. New operator for the data domain: Introduce a new, separate operator .[] for accessing collection elements. Use it for all new collection types, implement it for arrays, too.
  2. Rededicate [] to be used for the data domain only: Default behavior remains as is, but can be overridden in collection classes (including arrays). Using [] for property access is deprecated, you must use methods such as Object.setOwnProperty() and Object.getProperty() to do so.

Addendum: edited quote from a recent email of mine:

# Allen Wirfs-Brock (12 years ago)

On Nov 4, 2011, at 6:44 PM, Mark S. Miller wrote:

On Fri, Nov 4, 2011 at 10:37 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

As an aside: This problem would go away if we really did distinguish between accessing a property and accessing a collection element. Then the former would be done via Object.* methods, while the latter would be done via square brackets.

I admit that I haven't followed the previous thread on ".[" and such. Is there a short summary? I ask because my diagnosis is similar but my conclusion is reversed. The lesson I take from this is not to use objects as maps. It you want a map, create a Map() and say map.get(key) and map.set(key, value) rather than using square brackets.

My original proposal is at esdiscuss/2011-October/017468

Here is an abridged version:

We then give MemberExpression [ Expression ] a new semantics. Here is the initial skeleton of this new semantics:

if MemberExpression is a "collection" return the result of invoking its "element getter/setter method"
else do the algorithm from ES5 11.2.1

What this says is that for any existing ES5 style object continue to work just like they always have. But in ES.Harmony there would be a new kind of "collection" object for which . works differently from [ ].

So to make the above skeleton semantics more meaningful we need to define what we really mean by "collection" and by "element getter/setter method". Let's start with the latter.

Let assume that there are two predefined private name object values that are required to exist by the ES.Harmony spec. Let's refer to those values as @elementGetKey and @elementSetKey (these are just names we use in the spec. language to talk about those private name values, the actual private name objects would be dynamically provided by ES implementations). Then a "element getter/setter method" is simply an object property whose property key is either @elementGetKey or @elementSetKey. The signature of these methods would normally be: function /*element getter */ (elementKey){return anElementValue};
function /*element setter */ (elementKey, newElementValue){};

Further more we define "collection" to mean an object that has a property that is a "element getter/setter method". The property may be either own or inherited.

How would we define such an "collection" object. It could be as simply as something like this:

import {collectionGetter, collectionSetter} from "@metaCollections";

export function StringKeyedMap() { this.__content = Object.create(null); //note __content object is a "normal object" and [ ] on it does regular property access Object.defineProperty(this, collectionGetter,{value: function(k) {return this.__content[k]}); Object.defineProperty(this, collectionSetter,{value: function(k,v) {this.__content[k]=v;}); this.size = function() {Object.getOwnPropertyNames(this.__content ).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this.__content,k}; this.delete = function(k) {return delete this.__content[k]} }

This implements a string-keyed map with the same interface as used in the simple _map proposal, except that [ ] is used instead of get/set methods for element access. Note that there is no conflict between element names and method names such as "size" and "has". It uses as backing store a regularly object that acts as an string-keyed hash table.

Using this techniques all sorts of "collection" classes could be build including array-like collections with domain restrictions of their element values. They would all be fully "subclassable".

Also, the length invariant semantics of built-in array objects can be emulated without having to use Proxies.

# David Herman (12 years ago)

Perhaps proto should not be writeable in "use strict"?

That's a great idea! This never occurred to me, and I have not heard anyone suggest this. Thanks!

Doesn't work.

obj[(function(__){return __ + "proto" + __})("__")]
# Mark S. Miller (12 years ago)

On Tue, Nov 8, 2011 at 3:33 PM, David Herman <dherman at mozilla.com> wrote:

Perhaps proto should not be writeable in "use strict"?

That's a great idea! This never occurred to me, and I have not heard anyone suggest this. Thanks!

Doesn't work.

obj[(function(__){return __ + "proto" + __})("__")]

If the "[" above is a strict "[", it should not be able to address "proto", regardless of whether the "proto" is computed or not. Or if we intend only to suppress writing, then

 obj[(function(__){return __ + "proto" + __})("__")] = {}

should still fail if the "[" above is in strict code.

# Mark S. Miller (12 years ago)

On Tue, Nov 8, 2011 at 3:46 PM, Mark S. Miller <erights at google.com> wrote:

On Tue, Nov 8, 2011 at 3:33 PM, David Herman <dherman at mozilla.com> wrote:

Perhaps proto should not be writeable in "use strict"?

That's a great idea! This never occurred to me, and I have not heard anyone suggest this. Thanks!

Doesn't work.

obj[(function(__){return __ + "proto" + __})("__")]

If the "[" above is a strict "[", it should not be able to address "proto", regardless of whether the "proto" is computed or not. Or if we intend only to suppress writing, then

 obj[(function(__){return __ + "proto" + __})("__")] = {}

should still fail if the "[" above is in strict code.

Sorry, it should not fail. It should simply create a normal property that happens to be named "proto". Likewise, your first example should simply address such a normal property. Then JSON would again be an almost-subset of ES5/strict, modulo \u2028 and \u2029.

# David Herman (12 years ago)

And another silent semantic change? I wouldn't be so quick to do that. And that's not the direction we were going for proto in the last f2f.

I wish proto were just treated as another normal property. And I'd like for us to work towards a future where that's the case. I'm just skeptical we can do it by cramming it into strict mode.

# Mark S. Miller (12 years ago)

www.google.com/support/forum/p/Google+Docs/thread?tid=0cd4a00bd4aef9e4 But yes. Because the difference would be silent, I'm skeptical too.

# Brendan Eich (12 years ago)

One more thought: people are already avoiding "use strict"; because it bites back:

  • concatenation with non-strict code and under-testing, but let's hope we are past this now;
  • performance dinged a bit by strict mode, or at least non-strict calling strict and vice versa.

We should try to avoid taking away the forbidden fruit prematurely. In particular since ES6 is based on ES5 strict.

When <| is out and well-deployed, maybe. No predictable schedule for when we could actually hope to remove proto.