unknownPrivateSymbol trap (was: WeakMap better than Private Symbols? (was: direct_proxies "problem"))
On Thu, Jan 10, 2013 at 1:24 PM, David Bruant <bruant.d at gmail.com> wrote:
I think the return true/false protocol should be replaced by a return/throw protocol (return value is ignored). It'd be much more explicit this way.
FWIW, I kinda like the aesthetics of return/throw a bit better too, through it's kinda weird that the thrown error is also ignored (and instead a TypeError is thrown).
David Bruant wrote:
[Cc'ing Tom and Mark to be sure there is agreement on what I'm claiming in this message]
Le 10/01/2013 22:10, Brendan Eich a écrit :
Nathan Wall wrote:
Brendan Eich:
No, not if the symbol is not in the whitelist. Zero information leak is required. That's good news too. Objection withdrawn.
Maybe I gave up too easy :). Is the
unknownPrivateSymbol
trap called? What's the rationale for this trap?I just wrote that the trap is not even called if the symbol is not in the whitelist passed in when the proxy is created. No, the unknownPrivateSymbol trap is called when the symbol is not in the whitelist, so, as Nathan fears, a malicious proxy could throw and cancel the access to the private property.
Of course, and my description was for a "knownPrivateSymbol" trap! Shows how much I know :-P. Waiting to hear from Tom on this. Thanks to Nathan for being a squeaky wheel.
I think the return true/false protocol should be replaced by a return/throw protocol (return value is ignored). It'd be much more explicit this way.
Agreed.
Le 10/01/2013 22:31, Tab Atkins Jr. a écrit :
On Thu, Jan 10, 2013 at 1:24 PM, David Bruant <bruant.d at gmail.com> wrote:
I think the return true/false protocol should be replaced by a return/throw protocol (return value is ignored). It'd be much more explicit this way. FWIW, I kinda like the aesthetics of return/throw a bit better too, through it's kinda weird that the thrown error is also ignored (and instead a TypeError is thrown).
I didn't mention it, but I was thinking of keeping the error as it's thrown in the trap, only ignoring the return value in the 'return' case... I realize that I should have mention it now that I read about the TypeError in the strawman.
2013/1/10 Brendan Eich <brendan at mozilla.com>
David Bruant wrote:
[Cc'ing Tom and Mark to be sure there is agreement on what I'm claiming in this message]
Le 10/01/2013 22:10, Brendan Eich a écrit :
Nathan Wall wrote:
Brendan Eich:
No, not if the symbol is not in the whitelist. Zero information leak is required.
That's good news too. Objection withdrawn.
Maybe I gave up too easy :). Is the
unknownPrivateSymbol
trap called? What's the rationale for this trap?I just wrote that the trap is not even called if the symbol is not in the whitelist passed in when the proxy is created.
No, the unknownPrivateSymbol trap is called when the symbol is not in the whitelist, so, as Nathan fears, a malicious proxy could throw and cancel the access to the private property.
Of course, and my description was for a "knownPrivateSymbol" trap! Shows how much I know :-P. Waiting to hear from Tom on this. Thanks to Nathan for being a squeaky wheel.
The "unknownPrivateSymbol" trap would indeed be called when a private symbol not on the proxy's whitelist is encountered.
As far as I recall, the purpose of the trap was to allow a membrane or revocable proxy to explicitly abort accesses involving such private symbols. The point being that if a membrane can't abort such accesses, then collaborators on both sides of the membrane could circumvent the membrane by communicating via a previously agreed upon private symbol. I previously argued that symbols should actually be treated like primitives when crossing a membrane, so it'd even be easy for the collaborators to pass each other the private symbol.
That said, the worst a malicious proxy can do here is to cancel the property access. This fails noisily, not silently. It's basically a denial of service. We don't really protect against that at all. A proxy could throw in all of its traps. Actually a deviously malicious proxy could just go into an infinite loop rather than throwing ;-)
So yes, WeakMaps are the tool of choice if you want to be absolutely sure the object used as key cannot interfere with the lookup.
I think the return true/false protocol should be replaced by a
return/throw protocol (return value is ignored). It'd be much more explicit this way.
Agreed.
I would be fine with that, although it might be a bit inconsistent with other traps like "set", "defineProperty", etc. which all return booleans to indicate success.
That said, this trap is already the odd one out: it doesn't really make sense to define a Reflect.unknownPrivateSymbol method either, like we did for all other traps. IOW, unknownPrivateSymbol is really more like a notification callback than a real trap that gets to return some useful value.
Le 15/01/2013 20:32, Tom Van Cutsem a écrit :
2013/1/10 Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>>
David Bruant wrote: [Cc'ing Tom and Mark to be sure there is agreement on what I'm claiming in this message] Le 10/01/2013 22:10, Brendan Eich a écrit : Nathan Wall wrote: Brendan Eich: No, not if the symbol is not in the whitelist. Zero information leak is required. That's good news too. Objection withdrawn. Maybe I gave up too easy :). Is the `unknownPrivateSymbol` trap called? What's the rationale for this trap? I just wrote that the trap is not even called if the symbol is not in the whitelist passed in when the proxy is created. No, the unknownPrivateSymbol trap is called when the symbol is not in the whitelist, so, as Nathan fears, a malicious proxy could throw and cancel the access to the private property. Of course, and my description was for a "knownPrivateSymbol" trap! Shows how much I know :-P. Waiting to hear from Tom on this. Thanks to Nathan for being a squeaky wheel.
The "unknownPrivateSymbol" trap would indeed be called when a private symbol not on the proxy's whitelist is encountered.
As far as I recall, the purpose of the trap was to allow a membrane or revocable proxy to explicitly abort accesses involving such private symbols. The point being that if a membrane can't abort such accesses, then collaborators on both sides of the membrane could circumvent the membrane by communicating via a previously agreed upon private symbol.
Yes, we've discussed that in length with Nathan Wall :-)
I think the return true/false protocol should be replaced by a return/throw protocol (return value is ignored). It'd be much more explicit this way. Agreed.
I would be fine with that, although it might be a bit inconsistent with other traps like "set", "defineProperty", etc. which all return booleans to indicate success.
There is a precedent for defineProperty because [[DefineOwnProperty]] returns a boolean. After-refactor-[[Set]] also returns a boolean, so it makes sense the set operation does too.
That said, this trap is already the odd one out: it doesn't really make sense to define a Reflect.unknownPrivateSymbol method either, like we did for all other traps. IOW, unknownPrivateSymbol is really more like a notification callback than a real trap that gets to return some useful value.
I agree with this view.
2013/1/15 David Bruant <bruant.d at gmail.com>
Le 15/01/2013 20:32, Tom Van Cutsem a écrit :
As far as I recall, the purpose of the trap was to allow a membrane or revocable proxy to explicitly abort accesses involving such private symbols. The point being that if a membrane can't abort such accesses, then collaborators on both sides of the membrane could circumvent the membrane by communicating via a previously agreed upon private symbol.
Yes, we've discussed that in length with Nathan Wall :-)
Sorry, I only now catched up with that. I don't have anything more to add to that discussion.
So in conclusion:
- we still need unknownPrivateSymbol
- a return/throw protocol may be more suitable than a boolean return value for this "trap"
Tom Van Cutsem wrote:
2013/1/15 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>
Le 15/01/2013 20:32, Tom Van Cutsem a écrit :
As far as I recall, the purpose of the trap was to allow a membrane or revocable proxy to explicitly abort accesses involving such private symbols. The point being that if a membrane can't abort such accesses, then collaborators on both sides of the membrane could circumvent the membrane by communicating via a previously agreed upon private symbol.
Yes, we've discussed that in length with Nathan Wall :-)
Sorry, I only now catched up with that. I don't have anything more to add to that discussion.
So in conclusion:
- we still need unknownPrivateSymbol
Is it needed? Maybe the "fail/forward" decision canbe set beforehand, when creating the proxy. Then, no trap will be called (but you cannot change the response in time; OTOH, is there a use case for it?).
Or does it do something more sophisticated then just telling "forward" or "fail" for situation that I am accessing some unknown private symbol?
2013/1/15 Herby Vojčík <herby at mailbox.sk>
Is it needed? Maybe the "fail/forward" decision canbe set beforehand, when creating the proxy. Then, no trap will be called (but you cannot change the response in time; OTOH, is there a use case for it?).
Or does it do something more sophisticated then just telling "forward" or "fail" for situation that I am accessing some unknown private symbol?
Here's one example where the decision would need to be dynamic: a revocable reference (not a membrane) that wants to just forward all operations to the target until revoked. When revoked, it does not want to forward anything anymore, including private symbol accesses.
That said, if this is not an important use case, I'm open to making the decision static. I'm not too fond of the "unknownPrivateSymbol" trap.
Tom Van Cutsem wrote:
2013/1/15 Herby Vojčík <herby at mailbox.sk <mailto:herby at mailbox.sk>>
Is it needed? Maybe the "fail/forward" decision canbe set beforehand, when creating the proxy. Then, no trap will be called (but you cannot change the response in time; OTOH, is there a use case for it?). Or does it do something more sophisticated then just telling "forward" or "fail" for situation that I am accessing some unknown private symbol?
Here's one example where the decision would need to be dynamic: a revocable reference (not a membrane) that wants to just forward all operations to the target until revoked. When revoked, it does not want to forward anything anymore, including private symbol accesses.
These use cases can be made dynamic be some kind of
Proxy.fromNowOnForwardUnknownPrivateSymbols(proxy, false);
API. This is not truly dynamic. I'd say no truly dynamic use cases exist, where you must decide at_the_exact_point_of_access. IMO.
These use cases can be made dynamic be some kind of
Proxy.fromNowOnForwardUnknownPrivateSymbols(proxy, false);
API. This is not truly dynamic. I'd say no truly dynamic use cases exist, where you must decide at_the_exact_point_of_access. IMO.
I would be ok with something more along the lines of:
Proxy.preventAllFurtherCommunicationsThrough(proxy);
Meaning that the target becomes inaccessible through the proxy, through both traps that were specified in the handler and implicit forwarding, including private symbols.
One of the major problems I have with unknownPrivateSymbol
can be represented in the following case:
let create = Object.create, getOwnPropertyNames = Object.getOwnPropertyNames,
$size = new Symbol(/* private /), $data = new Symbol(/ private */);
class StringMap {
constructor() { this[$size] = 0; this[$data] = create(null); }
set(key, value) {
let data = this[$data];
if (!data) throw new TypeError('this object must be a StringMap');
if (!(key in data)) this[$size]++;
data[key] = value;
}
get(key) {
let data = this[$data];
if (!data) throw new TypeError('this object must be a StringMap');
return data[key];
}
size() { return this[$size]; }
keys() {
let data = this[$data];
if (!data) throw new TypeError('this object must be a StringMap');
return getOwnPropertyNames(data);
}
}
I think it is wrong that a Proxy should be allowed to introduce an inconsistency in my object by permitting me to increase the private size property but then preventing me from adding the new key to the data set. For example:
function getBrokenMap() {
let map = new StringMap(); map.set('apple', 'red'); map.set('kiwi', 'green');
let beEvil = false, evilProxy = new Proxy(map, { unknownPrivateSymbol: function() { if (beEvil) throw new Error(); beEvil = true; } });
try { map.set('lemon', 'yellow'); } catch(x) { }
return map;
}
let map = getBrokenMap();
map.size(); // => 3 map.keys().length; // => 2
I think this is a problem from the standpoint of a library developer who wants to ensure the integrity of one's components.
In the discussion with David, I think we agreed that it is possible for a membrane to prevent communication between two objects without unknownPrivateSymbol
given (1) they don't know each other directly and (2) no other third party knows both of them directly. We also agreed that given either 1 or 2, the objects could establish means to communicate behind the membrane's back regardless of whether unknownPrivateSymbol
exists.
In light of these considerations, I would strongly urge the removal of unknownPrivateSymbol
. It allows breaking all private symbol communication in one fell swoop, but it also allows other things which, in my opinion, should not be allowed. As Brendan said, it's really none of your business to be able to interfere with my object's internals -- including, I would add, internal access to private symbols you don't know about.
If you want a way to cut communications, how about a function that disables all traps and all forwarding on the proxy (including private symbol forwarding)? That prevents the public method set(key, value)
from being called which uses the internal private keys, so it doesn't mess up the integrity of my object.
Nathan
set(key, value) {
let data = this[$data];
if (!data) throw new TypeError('this object must be a StringMap');
if (!(key in data)) this[$size]++;
data[key] = value;
}
Doh! I wrote that in such a way that an inconsistency cannot be introduced, since $data
is retrieved before $size
is changed, and then the mutation on the data object happens without the use of a private symbol.
However, I think you get the idea.
Probably a better example would have been one where I wanted to mutate two private symbol properties with primitive values, in which case they would both require calls to unknownPrivateSymbol
when being set.
Nathan
Ok, thanks for your clarifying example.
I see your point. Basically you want obj[privateSymbol] to be a high-integrity operation that proxies cannot even abort (unless they know about the private symbol). That would give private-symbol-keyed-property-access a special status, since other important operations like Object.isFrozen(obj) can also be aborted by proxies.
It's important to reiterate that the proxy can only make the code fail noisily. It can't hide that it's being evil. In that sense it does not violate the integrity of the abstraction.
That said, I welcome suggestions to get rid of this trap.
Re. your suggestion for a primitive that inadvertently cuts all access to a proxy's target. That already exists via revocable proxies:
let {proxy, revoke } = Proxy.revocable(target, handler); revoke(); // proxy nulls out its target, it can't forward anything anymore (including private symbols it doesn't know about)
See strawman:revokable_proxies
However, I'm not yet sure how a membrane could make use of revocable proxies efficiently. In the simplest membrane code, all proxies of the same membrane share a single boolean flag that controls whether all of these proxies are active or revoked. Setting the flag to false instantly revokes all proxies at once.
If all proxies of a membrane are revocable, the membrane would need to store all of their revoke() functions in a collection and call them all when the membrane as a whole is revoked. That feels way more expensive. Then again, I think this might be needed anyway if we want to ensure that a revoked membrane releases all pointers to its enclosed object graph, so that those objects can be GC-ed...
Cheers, Tom
2013/1/16 Nathan Wall <nathan.wall at live.com>
[Cc'ing Tom and Mark to be sure there is agreement on what I'm claiming in this message]
Le 10/01/2013 22:10, Brendan Eich a écrit :
No, the unknownPrivateSymbol trap is called when the symbol is not in the whitelist, so, as Nathan fears, a malicious proxy could throw and cancel the access to the private property.
Quoting this very page: "If the private name is not on the whitelist, the proxy calls a new unknownPrivateName(target) trap." (the "new" refers to the fact that without private symbol interaction, this trap wouldn't be necessary)
I've recently posted an example of how proxies interact with private symbols and the whitelist [1]. That's at least my understanding.
I'd like to take the occasion of this thread-fork to address a point in the proxies_names strawman. Quote:
I think the return true/false protocol should be replaced by a return/throw protocol (return value is ignored). It'd be much more explicit this way.
David
[1] esdiscuss/2013-January/027951