Section 13.2: Rationale for "caller", "arguments" restriction
On Tue, May 26, 2009 at 6:05 PM, Mark S. Miller <erights at google.com> wrote:
function laundry(obj, verb, args) { "use strict"; return obj[verb].apply(obj, args); }
A better laundry:
function laundry(fun, thisArg, args) {
"use strict";
return fun.apply(thisArg, args);
}
On Tue, May 26, 2009 at 8:05 PM, Mark S. Miller <erights at google.com> wrote:
To fix this problem, we need one additional bit of poison: If arguments.caller or bar.caller would reveal a strict function, they should return something harmless instead. I suggest that undefined is a perfectly fine harmless value.
No, if adding "use strict" is going to break real-world code, ES5 should throw an exception.
I hope I don't need to justify that stance; we've been there, heard that, right? (The arguments have to do with workaday web developers who aren't designing object-capability sandboxes.) I'm not aware of any silent behavior changes in strict mode as defined in the draft.
Throwing here lets the implementation provide an error message that clearly blames the caller's strictness. Or, more properly, Mark himself: "Strict function f can't call nonstrict function g because g uses arguments.caller, which would expose f to tampering if g is evil. You can thank Mark Miller for this one." ;-)
(Here the message exposes that f is strict, and even its name, but I don't see how or why that is to be avoided.)
If this poison is not swallowed, then SES can still cope by translating all function calls to go through a harmless laundry function:
With getters and setters that will be quite a bit worse, right? Every property access would have to be translated into something that inspects the object rather carefully.
On 05/26/2009 06:05 PM, Mark S. Miller wrote:
JavaScript has one and only one encapsulation mechanism -- functions evaluating to closures that encapsulate their captured variables. However, the existence of these de-facto properties pokes a fatal hole in this mechanism, leaving us with no defensible boundary.
I appreciated the example on the slide you linked to. Providing .caller does seem to be problematic.
However, what you wrote above confused me. The things encapsulated by a
closure are the variables it refers to from lexically enclosing scopes.
What you wrote suggests that .caller somehow makes those available to
others, which isn't so, as far as I can tell. The closure's arguments
can be obtained, but those are different.
The mechanism with the hole is the dynamic link, not the static link.
On Tue, May 26, 2009 at 12:21 PM, Jim Blandy <jimb at mozilla.com> wrote:
In addition to the points made in Brendan's wonderfully informative message, I'll add one.
JavaScript has one and only one encapsulation mechanism -- functions evaluating to closures that encapsulate their captured variables. However, the existence of these de-facto properties pokes a fatal hole in this mechanism, leaving us with no defensible boundary. By mandating that these properties be poisoned for strict functions, strict functions are mostly safe even from non-strict functions. The slides starting at < google-caja.googlecode.com/svn/trunk/doc/html/es5-talk/img39.html>,
explained starting at 41minutes into < www.youtube.com/watch?v=Kq4FpMe6cRs> shows a simple vivid example.
At the time I gave this talk, I thought it actually made strict functions safe from non-strict functions, without needing the "mostly" qualifier. However, since then, I have noticed a wide gaping hole remaining. When a (let's say) powerful strict function foo calls an untrusted non-strict function bar, then bar ideally should obtain access only to the arguments that foo explicitly passes to it. This being JavaScript, we all know that bar also obtains access to the "this" object that foo also effectively passed to it. (In the SES context, this is known as the "exophoric function problem", and can probably be worked around by low overhead translation.)
The new hole I just noticed: Although foo's poisoned properties prevent bar from accessing foo's encapsulated variables, bar can still use the the de-facto "arguments.caller" or "bar.caller" to obtain access to foo itself. If the subsystem containing foo had wished to encapsulate foo itself, this reflection on bar's part violates the encapsulation of its calling subsystem.
To fix this problem, we need one additional bit of poison: If arguments.caller or bar.caller would reveal a strict function, they should return something harmless instead. I suggest that undefined is a perfectly fine harmless value. If this poison is not swallowed, then SES can still cope by translating all function calls to go through a harmless laundry function:
in which case bar.caller or bar's arguments.caller would simply return the harmless laundry instead. However, I would really like to avoid a translation that doubles the number of function calls performed. If undefined is, for some reason, not acceptable as a harmess value, then we could provide the above laundry function as if we had done this translation but without paying the cost.