Hi,
This post is not intentioned to be a perfect and final guide, but rather
a conversation starter.
It's mostly triggered by what Andrea described as a potential attack
(assigning Object.prototype.get).
# Description of the JS runtime
To a first approximation (ignoring things like alert/prompt and weird
window resize event behaviors), a JavaScript runtime contains a heap, a
call stack and a message queue. Messages accumulate in the queue and are
treated one after the other. The time during which one message is
treated is called a turn. Messages are queued when a user clicks and a
listener listens or when a timeout fires, etc.
A message contains a function which, upon being called may call other
functions (hence the call stack). Functions manipulate memory, including
things in the heap. The heap contains a bunch of objects being
accessible either from the global object or the scope of the function
being called.
# Some properties
## Environment Mutability
Until ES5, pretty much everything in the heap could be modified at any
turn by anyone; new properties could be added/removed from the global
object, or the Object.prototype object for instance.
Sharing access to an object pretty much meant giving away the
possibility to anyone with access to this object to mess up with it.
ES5 introduces a couple of constructs like Object.preventExtensions and
Object.defineProperty(,,{configurable: false, writable: true|false}) to
enable a programmer to share some object but have some properties
enforced. It is probably the first time there are constructs that act on
objects to create permanent effects on them.
## During a turn
Stating the obvious, but the function running in a turn has the power to
do anything to whatever it has access to. Among the possible changes,
it's possible to Object.freeze any object the function has access to.
When some JS code is running during a turn, it can't be preempted by
some other code. The code has to end.
## First turn
There is a first turn.
## (Partial) conclusion
If malicious code runs, it can do whatever is possible during its turns.
The role of a defenser will be to reduce "whatever is possible" to
"nothing harmful".
First reaction, if the malicious code runs first, too bad, it can edit
anything in the JavaScript environment it has access to, including
modifying Object.prototype. The careful attacker will freeze
Object.prototype after having modified it.
The opposite is true too. If the defender runs first, it can defend its
environment. If the defender doesn't want Object.prototype to be
modified, it can freeze it. If it wants to add polyfills then freeze
everything, that's also a decent idea.
If during the first turn no one modifies anything in an harmful or
defensive way, the decision to attack or defend is let to the code
running in the next turn and so on.
# Categorisation of threats
## Threats that are unprotectable against
Programming languages or APIs sometimes leave holes by design that even
the most skilled programmers couldn't write defensive programs against.
If I recall, ES5 strict mode has gotten rid of all of them with the
notable exception of mutable Date.prototype that provides a covert channel.
SES is an example of script that does everything it can to protect the
JS environment (including fixing or working around almost-comformant
environments bugs!)
## Threats that can be protected against
From everything I have said, one clear thing is that protecting the
JavaScript is an opt-in. If you do not opt-in to a secure environment,
you're left to be a prey by default.
The list of potential attacks is probably endless.
Adding an Object.prototype.get function and waiting for people to use
Object.defineProperty is such an attack. Putting an accessor on
Array.prototype[0] is also a possibility.
# What does it mean for the language evolution?
The first category needs to be taken care of and is. Next features are
carefully thought out to avoid unprotectable threats.
The second category is a bit more debatable. There is tension due to the
fact that the vast majority of JS developers aren't aware that
JavaScript security requires an opt-in like SES and as a consequence are
victim of whatever buggy or malicious code they happen to add to their
webpages.
Given that security requires an opt-in anyway, is it worthwhile to
modify the language (like Object.defineProperty) to protect for some
cases but not others?
I would tend to say no.
Regarding Andrea's specific case, I'd like to mention that proxy
handlers are also based on [[Get]]. The inheritance is actually used in
some cases for trap reuse (it was even more the case in the previous
design, but still is the case). Should inheritance be prevented here too
and ruin a valid use case? Just because an attacker might add an
Object.prototype.someTrap and the defender might have forgotten to
protect against it?
I don't think it's worth the cost in this case.
David
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss