Non-extensible WeakMaps

# David Bruant (14 years ago)

In Firefox Aurora as well as in Chromium 18, running the following

var wm = new WeakMap(); var o = {};

Object.preventExtensions(wm);

wm.set(o, 1); console.log(wm.get(o)); // 1

Is this something that is wanted? Same question for Maps and Sets.

# Tab Atkins Jr. (14 years ago)

On Sun, Jan 22, 2012 at 10:28 AM, David Bruant <bruant.d at gmail.com> wrote:

Hi,

In Firefox Aurora as well as in Chromium 18, running the following

var wm = new WeakMap(); var o = {};

Object.preventExtensions(wm);

wm.set(o, 1); console.log(wm.get(o)); // 1

Is this something that is wanted? Same question for Maps and Sets.

Calling set() on a WeakMap doesn't add any properties to the WeakMap object, so yes, it's expected that preventExtensions has no effect.

# David Bruant (14 years ago)

Le 22/01/2012 20:16, Tab Atkins Jr. a écrit :

On Sun, Jan 22, 2012 at 10:28 AM, David Bruant <bruant.d at gmail.com> wrote:

Hi,

In Firefox Aurora as well as in Chromium 18, running the following

var wm = new WeakMap(); var o = {};

Object.preventExtensions(wm);

wm.set(o, 1); console.log(wm.get(o)); // 1

Is this something that is wanted? Same question for Maps and Sets. Calling set() on a WeakMap doesn't add any properties to the WeakMap object, so yes, it's expected that preventExtensions has no effect.

I agree that Object.preventExtensions is defined as preventing addition of new properties. Likewise, Object.freeze and Object.seal only act on object properties (extended to private properties?). But the broader problem they are addressing is reducing the mutability of objects. WeakMaps, maps and sets bring a new form of mutability which cannot be implemented in the form of private properties (I think at least). So the question is:

Should Object.preventExtensions be extended to reduce WeakMaps, Maps and Sets mutability? Likewise for Object.seal|freeze?

# Herby Vojčík (14 years ago)

David Bruant wrote:

I agree that Object.preventExtensions is defined as preventing addition of new properties. Likewise, Object.freeze and Object.seal only act on object properties (extended to private properties?). But the broader problem they are addressing is reducing the mutability of objects. WeakMaps, maps and sets bring a new form of mutability which cannot be implemented in the form of private properties (I think at least). So the question is:

Should Object.preventExtensions be extended to reduce WeakMaps, Maps and Sets mutability? Likewise for Object.seal|freeze?

It would be special case. I'd say no. Collections should proabably get their own API for this, analogic to Object.preventExtension|seal|freeze.

Where to put it, is the question. It should probably be callable like Map.seal(aMap), Array.freeze(anArray) etc. If a collection hierarchy has common ancestor, constructor function can inherit in parallel with prototypes, so it may in fact reside in that(ose) common base classes.

David

Herby

P.S.: Array.xxx version should be able to work only on indexed elements, not or other properties (it is Object.xxx's work). Of course them same for Map etc., but there it is natural.

# Brendan Eich (14 years ago)

Herby Vojčík <mailto:herby at mailbox.sk> January 22, 2012 11:42 AM David Bruant wrote:

I agree that Object.preventExtensions is defined as preventing addition of new properties. Likewise, Object.freeze and Object.seal only act on object properties (extended to private properties?).

No, we agreed the property visiting under freeze and seal does not affect private-object-named properties.

But the broader problem they are addressing is reducing the mutability of objects. WeakMaps, maps and sets bring a new form of mutability which cannot be implemented in the form of private properties (I think at least). So the question is:

Should Object.preventExtensions be extended to reduce WeakMaps, Maps and Sets mutability? Likewise for Object.seal|freeze?

It would be special case. I'd say no.

I agree so far as this goes.

Mark has thought deeply about this topic, with the use-case of preventing extensions being the closing of covert or side channels in JS objects. For private names and weak maps, there is no channel to close via preventExtensions, since the attacker by definition doesn't have the key. For Maps and Sets, which support enumeration, the thread model is different.

# David Bruant (14 years ago)

Le 22/01/2012 21:11, Brendan Eich a écrit :

Herby Vojčík <mailto:herby at mailbox.sk> January 22, 2012 11:42 AM David Bruant wrote:

I agree that Object.preventExtensions is defined as preventing addition of new properties. Likewise, Object.freeze and Object.seal only act on object properties (extended to private properties?).

No, we agreed the property visiting under freeze and seal does not affect private-object-named properties.

Ok.

But the broader problem they are addressing is reducing the mutability of objects. WeakMaps, maps and sets bring a new form of mutability which cannot be implemented in the form of private properties (I think at least). So the question is:

Should Object.preventExtensions be extended to reduce WeakMaps, Maps and Sets mutability? Likewise for Object.seal|freeze?

It would be special case. I'd say no.

I agree so far as this goes.

Mark has thought deeply about this topic, with the use-case of preventing extensions being the closing of covert or side channels in JS objects. For private names and weak maps, there is no channel to close via preventExtensions, since the attacker by definition doesn't have the key.

I'm not sure to understand what you're calling "/the/ key". WeakMap.prototype, being itself a weakmap, does provide a covert channel (since everyone has access to the same primordial objects identities). Of course, it can be repaired [1], but it's quite unfortunate to have to "repair" a feature that isn't part of a standard yet, isn't it?

I'm seeing that it is suggested [2] that WeakMap.prototype.set(k,v) and WeakMap.prototype.delete(k) throw a TypeError (instead of trying to mutate WeakMap.prototype). Instead of WeakMap.prototype being a non-mutable weakmap, what about it being not a weakmap at all? (and throw for .has and .get as well)

For Maps and Sets, which support enumeration, the thread model is different.

Allowing any type to be a key also changes the threat model, because it means that prior arrangement can be enough to communicate.

However, besides the {WeakMap|Map|Set}.prototype, I do not see anything that cannot be controlled with a revocable caretaker.

David

[1] code.google.com/p/es-lab/source/browse/trunk/src/ses/initSES.js#2237 [2] bugzilla.mozilla.org/show_bug.cgi?id=656828

# Brendan Eich (14 years ago)

David Bruant <mailto:bruant.d at gmail.com> January 22, 2012 12:54 PM I'm not sure to understand what you're calling "/the/ key". WeakMap.prototype, being itself a weakmap, does provide a covert channel (since everyone has access to the same primordial objects identities). Of course, it can be repaired [1], but it's quite unfortunate to have to "repair" a feature that isn't part of a standard yet, isn't it?

Yes, you're right -- the prototypes require special handling for some of these classes. We've talked about this at past meetings. It's unfortunate that in JS, a prototype is not instanceof the objects of its kind.

There is a tension between making prototypes be blank Object instances and making them as if they were the first of their kind. For the built-ins, we did the latter up till ES3, where RegExp.prototype was spec'ed as Object. No implementation followed that part of ES3, and ES5 matched reality.

We would like a principled way for built-in constructors that have such covert channel hazards to opt out of their prototype being of their kind.

For Maps and Sets, which support enumeration, the threa[t] model is different. Allowing any type to be a key also changes the threat model, because it means that prior arrangement can be enough to communicate.

True but it's only with such arrangement, which would be a different kind of bug from any in the case where a key could be forged. In the forged key case, there's no defense.

However, besides the {WeakMap|Map|Set}.prototype, I do not see anything that cannot be controlled with a revocable caretaker.

Right, and these are needed in general in same-frame mashups anyway (if I understand correctly). Mark is Jedi master here, he should weigh in.

# Andrea Giammarchi (14 years ago)

for what it's worth it ...

Object.defineProperty(Object.prototype, "new", {value: function (descriptor) { return Object.create(typeof this == "function" ? this.prototype : this, descriptor || {} ); }});

Map.new() and Math.new() should both work

my 2 cents

# Brendan Eich (14 years ago)

We're not going to force everyone to use Map.new, so while this might be nice new API, it doesn't solve the problem of the prototype channel.

# David Bruant (14 years ago)

Le 23/01/2012 17:25, Brendan Eich a écrit :

David Bruant <mailto:bruant.d at gmail.com> January 22, 2012 12:54 PM I'm not sure to understand what you're calling "/the/ key". WeakMap.prototype, being itself a weakmap, does provide a covert channel (since everyone has access to the same primordial objects identities). Of course, it can be repaired [1], but it's quite unfortunate to have to "repair" a feature that isn't part of a standard yet, isn't it?

Yes, you're right -- the prototypes require special handling for some of these classes. We've talked about this at past meetings. It's unfortunate that in JS, a prototype is not instanceof the objects of its kind.

There is a tension between making prototypes be blank Object instances and making them as if they were the first of their kind. For the built-ins, we did the latter up till ES3, where RegExp.prototype was spec'ed as Object. No implementation followed that part of ES3, and ES5 matched reality.

We would like a principled way for built-in constructors that have such covert channel hazards to opt out of their prototype being of their kind.

Can you elaborate on this last point? Do you mean a run-time way? Does "opt-out" mean that the default would be that Constr.prototype is always an instance of Constr? It's been standardized this way to match reality, but it doesn't mean it was a good design decision. It's not too late to change this.

A "spec" principle could be that no new built-in (any built-in, not just the Constr.prototype) provide an irrevocable access to a shared mutable state. "built-in" here, actually refers to anything 2 scripts can irrevocably.

Mutable state of objects available to everyone are almost systematically a source of covert chanel. It's true for Date.prototype, WeakMap.prototype, Map.prototype, etc. 2 scripts having access to the same built-ins and these built-in having a mutable state is what starts troubles.

For the record, this rule (no irrevocable access by default to objects with mutable state) was applied in the design of Joe-E [1], a subset of Java designed to allow object capabilities.

For Maps and Sets, which support enumeration, the threa[t] model is different. Allowing any type to be a key also changes the threat model, because it means that prior arrangement can be enough to communicate.

True but it's only with such arrangement, which would be a different kind of bug from any in the case where a key could be forged. In the forged key case, there's no defense.

I think that wrapping the object in a proxy and passing this proxy can save from pretty much any situation. Just an intuition. It assumes that the party I want to give access to an object via a proxy doesn't already have access to it (if it's global for instance).

David

[1] www.cs.berkeley.edu/~daw/papers/joe-e-ndss10.pdf

# Brendan Eich (14 years ago)

David Bruant <mailto:bruant.d at gmail.com> January 23, 2012 12:37 PM Can you elaborate on this last point? Do you mean a run-time way?

No, spec way.

For the record, this rule (no irrevocable access by default to objects with mutable state) was applied in the design of Joe-E [1], a subset of Java designed to allow object capabilities.

Right, Mark has mentioned that. Indeed it does argue for RegExp.prototype being of class Object (no .lastIndex shared mutable).