Whitelist WeakSet
2012/9/25 Waldemar Horwat <waldemar at google.com>
Yes. This was bothering me during the meeting and (as far as I know) didn't get resolved. What if someone passes in a proxy to a WeakSet instead of an actual WeakSet?
A. Allowed. Then the security protocol is utterly broken.
B. Disallowed. Now we have a class that can't be proxied.
Neither sounds good. What am I missing?
I intended option B. The whitelist can't be proxied.
It's not that WeakSets can't be proxied in general (they can), but for the specific use of a WeakSet as a whitelist, the proxy needs a genuine WeakSet. This isn't all that different from DOM methods requiring genuine DOM elements, etc.
I also don't see how membranes would require proxied whitelists: it would be weird to construct a Proxy whose whitelist must be a membraned object. I think of a newborn Proxy and its whitelist as being part of the same abstraction. It makes little sense to separate them by a membrane.
I previously mentioned the alternative of making the WeakSet entirely hidden and introducing two built-in methods to manipulate them (Proxy.addName(proxy, name) and Proxy.removeName(proxy, name)). Then the problem of proxied whitelists goes away, but it precludes easy sharing of the same whitelist among multiple proxies, which seems like a reasonable thing to do.
Le 25/09/2012 07:49, Tom Van Cutsem a écrit :
2012/9/25 Waldemar Horwat <waldemar at google.com <mailto:waldemar at google.com>>
Yes. This was bothering me during the meeting and (as far as I know) didn't get resolved. What if someone passes in a proxy to a WeakSet instead of an actual WeakSet? A. Allowed. Then the security protocol is utterly broken. B. Disallowed. Now we have a class that can't be proxied. Neither sounds good. What am I missing?
I intended option B. The whitelist can't be proxied.
It's not that WeakSets can't be proxied in general (they can), but for the specific use of a WeakSet as a whitelist, the proxy needs a genuine WeakSet. This isn't all that different from DOM methods requiring genuine DOM elements, etc.
I also don't see how membranes would require proxied whitelists: it would be weird to construct a Proxy whose whitelist must be a membraned object. I think of a newborn Proxy and its whitelist as being part of the same abstraction. It makes little sense to separate them by a membrane.
I'm thinking of an example like what can be seen in some of Mark's presentation with one actor creating 2 subgraphs of objects Alice and Bob. Thanks to some language property, Alice and Bob are only connected through their creator and any communication is mediated by this creator. Both subgraphs can be membraned so that their creator can stop any communication between both anytime. Now, let's say Alice wants to share knowledge of some private names with Bob. She can send a WeakSet. But as she does that, the mediation membrane wraps the WeakSet and Bob only ever gets the wrapped version. This wrapped version is unusable for Bob to create proxies (assuming the handler and Proxy constructor are provided by Bob).
Le 25/09/2012 02:05, P Horwat a écrit :
On 09/24/2012 01:24 AM, David Bruant wrote:
Le 24/09/2012 10:04, Tom Van Cutsem a écrit :
Right. Perhaps what Herby meant is that the proxy might provide a malicious whitelist to steal the names being looked up in them. This will be prevented by requiring the whitelist to be a genuine, built-in WeakSet. The proxy will use the built-in WeakSet.prototype.get method to lookup a name in that whitelist, so a proxy can't monkey-patch that method to steal the name either. True. I think a lot of that part depends on how WeakSet/Set are spec'ed. It might be possible to accept proxies wrapping WeakSets (which is likely to be helpful with membranes) and perform the check on the target directly, bypassing the proxy traps. Or maybe consider the built-in WeakSet.prototype.get method as a private named method on the weakset instance and only call the unknownPrivateName trap.
Yes. This was bothering me during the meeting and (as far as I know) didn't get resolved. What if someone passes in a proxy to a WeakSet instead of an actual WeakSet?
A. Allowed. Then the security protocol is utterly broken.
I disagree. Some definitions first:
-
Any proxy has a target. If this target is a proxy, it has a target, etc. Let's call that a proxy chain. Eventually, the target has to be a non-proxy object. Let's call that the end-target.
-
WeakSets are not spec'ed, but let's say for now each instance has internal own private-named [[add]], [[remove]] and [[has]] method. The public-facing methods on WeakSet.prototype just call the internal ones:
WeakSet.prototype.has (called with |this| and an object o):
- let weakset be |this|
- let has be weakset.[[has]] // [[has]] is a private name.
- return has(weakset, o)
So now, let's study the following:
var ws = new WeakSet();
var n = new Name();
ws.add(n);
ws = new Proxy(ws, handlerWS); // (3) wrapping and encapsulating the
weakset
var p = new Proxy({}, handlerP, ws); // (1)
p[n] = 37; // (2)
For (1), the Proxy constructor needs to make sure ws is a weakset instance. Verifying such a thing is not trappable (to prevent proxies to lie about that), so it an be performed on the object or the end-target directly, it makes no difference. So it's possible for the constructor to know that ws is a WeakSet or a proxy to a WeakSet, that part poses no problem.
For (2), the engine needs to check whether p knows n before calling the set trap. For that, it can call [[has]] on ws. Since ws is a proxy itself, [[Get]]-ing its [[has]] private property first requires to make sure ws knows the private name. It does not (3). So handlerWS.unknownPrivateName is called. If it doesn't throw, the operation is transparently forwarded to the target. Likewise when it's calling the [[has]] internal method on ws; forwarded to the target after handlerWS.unknownPrivateName decided not to throw.
In this last paragraph, I've demonstrated a way for the whitelist to be a proxy to a weakmap without ever needing to leak private names. It all rely on the internal [[has]] of weakset instances to be a private name known to no one. Being a private name is just for spec economy since the name will never be visible to any client code.
2012/9/25 David Bruant <bruant.d at gmail.com>
In this last paragraph, I've demonstrated a way for the whitelist to be a proxy to a weakmap without ever needing to leak private names. It all rely on the internal [[has]] of weakset instances to be a private name known to no one. Being a private name is just for spec economy since the name will never be visible to any client code.
Thanks for your worked-out example. I understand where you're going, but I still feel it introduces too much complexity for a questionable use case. Your proposal implicitly assumes that built-in functions such as [[has]] are represented as private names. That's not how built-in methods currently work. Also, you mention that it's just for spec economy, but that's not really true: if the WeakSet is a proxy and [[has]] is a private name, the proxy's unknownPrivateName trap would need to be called.
Besides, I repeat, this isn't the first case where objects can't be transparently proxied. Consider the following transcript (tested in Firefox nightly 18.01a, with support for direct proxies):
var e = document.createElement("div") var e2 = document.createElement("ul") e.appendChild(e2) // works fine var e3 = new Proxy(e2, {}) e.appendChild(e3) Exception... "Could not convert JavaScript argument arg 0 ..."
[cc'ing Eddy Bruel for his opinion. Search for your name below]
Le 26/09/2012 20:46, Tom Van Cutsem a écrit :
2012/9/25 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>
In this last paragraph, I've demonstrated a way for the whitelist to be a proxy to a weakmap without ever needing to leak private names. It all rely on the internal [[has]] of weakset instances to be a private name known to no one. Being a private name is just for spec economy since the name will never be visible to any client code.
Thanks for your worked-out example. I understand where you're going, but I still feel it introduces too much complexity for a questionable use case. Your proposal implicitly assumes that built-in functions such as [[has]] are represented as private names. That's not how built-in methods currently work. Also, you mention that it's just for spec economy, but that's not really true: if the WeakSet is a proxy and [[has]] is a private name, the proxy's unknownPrivateName trap would need to be called.
True. I didn't sort out all the details and it's biting back... The demonstration I gave isn't valid, but I'm sure it's not that far from being workable either. We can remove the part about [[has]] being a private name.
The engine can know if the end-target is a (Weak)Set, so it can decide to bypass the proxy chain entirely and check directly on the end target. Maybe the unknownPrivateName can be generalized to a "securityCheckSink" trap. The name is awful, but the idea is here.
Besides, I repeat, this isn't the first case where objects can't be transparently proxied.
This is however the first case within pure ECMAScript programs and I find that problematic for the very use case I exposed in the previous message (Alice and Bob both in different membranes trying to share a set of private names). I admit I don't have a perfect solution yet, but what I've proposed (and tweaked above) isn't that far from an efficient and minimalistic solution. If we can find such a solution, there is no reason not to use it. If all possible solutions we come up with are too complicated or inefficient to implement, let's accept it as a limitation.
Consider the following transcript (tested in Firefox nightly 18.01a, with support for direct proxies):
var e = document.createElement("div") var e2 = document.createElement("ul") e.appendChild(e2) // works fine var e3 = new Proxy(e2, {}) e.appendChild(e3) Exception... "Could not convert JavaScript argument arg 0 ..."
It does not currently work, however, it isn't clear whether it should. I'm convinced no work has been done in Firefox to make proxies and the DOM interoperable (cc'ing Eddy Bruel who implemented direct proxies in Firefox for his opinion). With the previous proxy design, it was clear that emulating native DOM objects with proxies was a no-go, because there was no way for the DOM tree to know whether a proxy could be assiocated with a native object. With the current design, however, the DOM can figure out whether the end target of a proxy chain is a genuine DOM object and potentially accept it as Node in the DOM tree if that's what the end target is. I can't tell whether it's a good or bad idea, whether it's worth the implementation cost, but the proxy design shift has re-opened that door.
2012/9/26 David Bruant <bruant.d at gmail.com>
With the current design, however, the DOM can figure out whether the end target of a proxy chain is a genuine DOM object and potentially accept it as Node in the DOM tree if that's what the end target is.
I can't tell whether it's a good or bad idea, whether it's worth the
implementation cost, but the proxy design shift has re-opened that door.
I agree it's tempting, but it's a dangerous road in the sense that it might pierce membranes (i.e. membranes having no place to stand when some built-in method inadvertently reaches into the built-in target object and exposes some object that it references). I'm not sure if there would be any such case in e.g. the DOM, but if we go down this road, it may become an issue in the future.
I agree we should strive to allow proxies in as much places as possible, but I don't feel like the proxy design would be broken if there are a few cases where this is not possible. Any system must eventually bottom out at some point.
On 09/24/2012 01:24 AM, David Bruant wrote:
Yes. This was bothering me during the meeting and (as far as I know) didn't get resolved. What if someone passes in a proxy to a WeakSet instead of an actual WeakSet?
A. Allowed. Then the security protocol is utterly broken.
B. Disallowed. Now we have a class that can't be proxied.
Neither sounds good. What am I missing?