Best way to protect against external JS affecting internal JS

124 views
Skip to first unread message

Benjamin Kalman

unread,
Mar 12, 2013, 6:56:43 PM3/12/13
to v8-u...@googlegroups.com
I'm on the Chrome Extensions team, and we've run into a problem where extensions override Array.prototype.forEach in a way that breaks our internal JS.

A workaround we've done is to write our own forEach method, but this problem is widespread - extensions also override JSON, document.createElement, etc - the vector for accidental breakage is as widespread as all of the JS and DOM libraries.

What is the best way to protect against this in a general way? The only safe thing I can think of is to run all our code in a separate context, but I've been told that creating contexts is an expensive operation. How expensive? Alternatively, apparently v8 has solved this problem internally by guaranteeing that it's running the builtin libraries - is/can this be exposed?

Cheers,
Ben.

Jakob Kummerow

unread,
Mar 13, 2013, 6:01:24 AM3/13/13
to v8-u...@googlegroups.com
On Tue, Mar 12, 2013 at 11:56 PM, Benjamin Kalman <kal...@chromium.org> wrote:
I'm on the Chrome Extensions team, and we've run into a problem where extensions override Array.prototype.forEach in a way that breaks our internal JS.

A workaround we've done is to write our own forEach method, but this problem is widespread - extensions also override JSON, document.createElement, etc - the vector for accidental breakage is as widespread as all of the JS and DOM libraries.

Welcome to JavaScript! Have you considered using a language with a sane specification?
 
What is the best way to protect against this in a general way?

There is none. Being able to monkey-patch everything is a "feature" of the language. Fun fact: some properties are read-only, so they can be relied upon. Or can they?
> Math.PI
3.141592653589793
> Math.PI = 4
> Math.PI
3.141592653589793
> Math = {PI: 4}
> Math.PI
4
 
The only safe thing I can think of is to run all our code in a separate context, but I've been told that creating contexts is an expensive operation. How expensive?

Have you tried benchmarking it? If you can't measure it, it's not important. If you can, you'll have data to help decide whether the difference is a problem in your use case or not.
 
Alternatively, apparently v8 has solved this problem internally by guaranteeing that it's running the builtin libraries - is/can this be exposed?

Sorry, no. 
You can lobby for an official way to retrieve the original unpatched implementations to be included in ECMAScript 7. The general problem with introducing sanity, however, is that you can't break existing code, which basically means that all the good stuff has to be opt-in, which in turn means that the original problem doesn't just go away, instead you'll still have to support it until it slowly dies out, if ever.

Crazy idea: how about a rule for the Chrome web store that forbids monkey-patching builtins? :-)


Cheers,
Ben.

--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to the Google Groups "v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to v8-users+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Chris Angelico

unread,
Mar 13, 2013, 8:36:59 AM3/13/13
to v8-u...@googlegroups.com
On Wed, Mar 13, 2013 at 9:01 PM, Jakob Kummerow <jkum...@chromium.org> wrote:
> The general problem with introducing sanity, however, is that you can't
> break existing code, which basically means that all the good stuff has to be
> opt-in, which in turn means that the original problem doesn't just go away,
> instead you'll still have to support it until it slowly dies out, if ever.

Which is why JavaScript/ECMAScript/v8/etc will probably never get true
Unicode support. *SIGH*. Sanity is hard to retrofit.

ChrisA

Michael Schwartz

unread,
Mar 13, 2013, 9:56:59 AM3/13/13
to v8-u...@googlegroups.com
Can Harmony Proxies be used to detect when the prototypes or builtins are being overridden?

If so, you could save the original and provide a new API to fetch the original.

Toon Verwaest

unread,
Mar 13, 2013, 10:23:27 AM3/13/13
to v8-u...@googlegroups.com
Once we have proper symbol (private names) support, you could use that to make your own internal API.

Andreas Rossberg

unread,
Mar 13, 2013, 10:27:42 AM3/13/13
to v8-u...@googlegroups.com
On 13 March 2013 14:56, Michael Schwartz <myk...@gmail.com> wrote:
> Can Harmony Proxies be used to detect when the prototypes or builtins are
> being overridden?
>
> If so, you could save the original and provide a new API to fetch the
> original.

I don't see how proxies help here. Saving the originals certainly
doesn't require proxies (nor private symbols).

Once ES6 actually becomes an option, however, module loaders could be
used to sandbox extensions into their own set of globals.

Short of that, immutability always is your best friend. Would it be
possible to freeze the globals that extensions are given access to?

/Andreas

jok...@gmail.com

unread,
Mar 13, 2013, 10:45:59 AM3/13/13
to v8-u...@googlegroups.com, jok...@gmail.com
Thank you.

-- pesan asli --
Subjek: Re: [v8-users] Best way to protect against external JS affecting internal JS
Dari: Toon Verwaest <verw...@chromium.org>
Tanggal: 13-03-2013 21.23

Once we have proper symbol (private names) support, you could use that to
make your own internal API.


On Wed, Mar 13, 2013 at 2:56 PM, Michael Schwartz <myk...@gmail.com> wrote:

> Can Harmony Proxies be used to detect when the prototypes or builtins are
> being overridden?
>
> If so, you could save the original and provide a new API to fetch the
> original.
>

Michael Schwartz

unread,
Mar 13, 2013, 10:49:55 AM3/13/13
to v8-u...@googlegroups.com
On Mar 13, 2013, at 7:27 AM, Andreas Rossberg <ross...@google.com> wrote:

On 13 March 2013 14:56, Michael Schwartz <myk...@gmail.com> wrote:
Can Harmony Proxies be used to detect when the prototypes or builtins are
being overridden?

If so, you could save the original and provide a new API to fetch the
original.

I don't see how proxies help here. Saving the originals certainly doesn't require proxies (nor private symbols).

A proxy would be able to detect Array.prototype.forEach is being stored to.

A proxy on window would be able to detect JSON being stored to.  

A proxy on document would be able to detect createElement being stored to.

&c

The extensions team can choose which object to proxy and when.

Last time I checked, Harmony extensions had to be enabled via command switch when launching Chrome.  That would preclude using proxies as I suggested here.


On Mar 12, 2013, at 3:56 PM, Benjamin Kalman <kal...@chromium.org> wrote:

I'm on the Chrome Extensions team, and we've run into a problem where extensions override Array.prototype.forEach in a way that breaks our internal JS.

A workaround we've done is to write our own forEach method, but this problem is widespread - extensions also override JSON, document.createElement, etc - the vector for accidental breakage is as widespread as all of the JS and DOM libraries.

What is the best way to protect against this in a general way? The only safe thing I can think of is to run all our code in a separate context, but I've been told that creating contexts is an expensive operation. How expensive? Alternatively, apparently v8 has solved this problem internally by guaranteeing that it's running the builtin libraries - is/can this be exposed?

Andreas Rossberg

unread,
Mar 13, 2013, 11:20:06 AM3/13/13
to v8-u...@googlegroups.com
On 13 March 2013 15:49, Michael Schwartz <myk...@gmail.com> wrote:
> On Mar 13, 2013, at 7:27 AM, Andreas Rossberg <ross...@google.com> wrote:
>>
>> On 13 March 2013 14:56, Michael Schwartz <myk...@gmail.com> wrote:
>>
>>> Can Harmony Proxies be used to detect when the prototypes or builtins are
>>> being overridden?
>>>
>>> If so, you could save the original and provide a new API to fetch the
>>> original.
>
>> I don't see how proxies help here. Saving the originals certainly doesn't
>> require proxies (nor private symbols).
>
> A proxy would be able to detect Array.prototype.forEach is being stored to.
>
> A proxy on window would be able to detect JSON being stored to.
>
> A proxy on document would be able to detect createElement being stored to.

Yes, but so what? What is needed is access to the original
definitions, not the information that they are no longer accessible
through their normal path.

/Andreas

Benjamin Kalman

unread,
Mar 13, 2013, 11:44:21 AM3/13/13
to v8-u...@googlegroups.com
Thanks for the responses, very interesting.

On Wed, Mar 13, 2013 at 3:01 AM, Jakob Kummerow <jkum...@chromium.org> wrote:
Welcome to JavaScript! Have you considered using a language with a sane specification?

Well, the other option is to try to write everything through the v8 API, which would be quite a bit of effort - we have 5000 non-whitespace/comment lines of JS, and ideally we don't want to rewrite it all. If we can come up with an easier-to-write DSL around the v8 API then that might be more viable, but I haven't put a lot of thought into that.

A detail that doesn't really concern v8: for things like document.createElement we need DOM APIs which the separate-context solution won't actually work easily without changes to webkit afaik. It also makes the write-in-C++ solution harder for similar reasons, we'd need to make sure every DOM API we ever call is available from chromium.


Have you tried benchmarking it? If you can't measure it, it's not important. If you can, you'll have data to help decide whether the difference is a problem in your use case or not.
 

Not really, we don't have any meaningful perf testing for extensions. I could try profiling I guess. A data point is that yesterday I found a context we were creating unnecessarily and fixed it, and it doesn't seem to have made any difference to page load speed (which is on the order of 500ms). This is promising.
 
Alternatively, apparently v8 has solved this problem internally by guaranteeing that it's running the builtin libraries - is/can this be exposed?

Sorry, no. 
You can lobby for an official way to retrieve the original unpatched implementations to be included in ECMAScript 7. The general problem with introducing sanity, however, is that you can't break existing code, which basically means that all the good stuff has to be opt-in, which in turn means that the original problem doesn't just go away, instead you'll still have to support it until it slowly dies out, if ever.


Getting into the world of ESFoo isn't really where I want to go with this. Strawman: if there were a 'use strict' for builtins, like 'use builtins', where for that file you get unpolluted APIs, that might solve our problems. However, I can see this breaking e.g. if I pass an array from one file to another, should it be 'instanceof Array'?

I guess what I was realistically asking about is some support at the v8 API level.
 
Crazy idea: how about a rule for the Chrome web store that forbids monkey-patching builtins? :-)

Not really feasible, monkey patching is an idiom that JS developers would get very upset about being taken away from them. Plus lots of libraries do it. We had a hard enough problem (and in fact failed) forbidding eval. And likewise freezing globals etc. Philosophically it's just sad if we forbid existing features of a platform because it's too hard to implement.

Lastly - I am not really sure about how harmony proxies work, but it sounds like we're told after they change not before they change? If not, that would certainly be a neater solution than what we're doing for JSON (saving function references on load), but I can see it getting messy with prototypes.

Cheers,
Ben.

Michael Schwartz

unread,
Mar 13, 2013, 11:55:47 AM3/13/13
to v8-u...@googlegroups.com
A Harmony Proxy looks like an object.  When ANY member is accessed, you have a function called that returns (or sets) the value.  Sort of like a wildcard getter and setter for the object in question.  There was mention a while ago about being able to proxy for the global or window object.  I am not sure if that's possible at this point.

I created a github repo a while ago with some experiments using proxies:


And the ultimate writeup (perhaps! :):


Jakob Kummerow

unread,
Mar 13, 2013, 12:07:51 PM3/13/13
to v8-u...@googlegroups.com
On Wed, Mar 13, 2013 at 4:44 PM, Benjamin Kalman <kal...@chromium.org> wrote:
Not really feasible, monkey patching is an idiom that JS developers would get very upset about being taken away from them. Plus lots of libraries do it. We had a hard enough problem (and in fact failed) forbidding eval. And likewise freezing globals etc. Philosophically it's just sad if we forbid existing features of a platform because it's too hard to implement.
 
Well, but "too hard to implement" is not the reason. You're not asking how you can implement monkey-patching; you're asking how you can stop/limit/blacklist/undo/circumvent it because it's harmful. The way I see it, the platform is shooting itself in the foot, repeatedly, but it gets angry at us when we suggest to take away its gun.

Lastly - I am not really sure about how harmony proxies work, but it sounds like we're told after they change not before they change? If not, that would certainly be a neater solution than what we're doing for JSON (saving function references on load), but I can see it getting messy with prototypes.

I guess the best you can do is before you hand the context over to the extension, create a copy of all the library functions/objects you care about. Then you can restore that as necessary when control comes back to your code. It should be possible to package this behavior relatively nicely; maybe a custom alternate global object helps (so you wouldn't have to restore anything, you'd just mechanically s/Array/original_global.Array/g in your code). You don't need proxies for this, nor any other way to observe what the extension is mucking with.

Benjamin Kalman

unread,
Mar 13, 2013, 12:32:11 PM3/13/13
to v8-u...@googlegroups.com
On Wed, Mar 13, 2013 at 9:07 AM, Jakob Kummerow <jkum...@chromium.org> wrote:
Well, but "too hard to implement" is not the reason. You're not asking how you can implement monkey-patching; you're asking how you can stop/limit/blacklist/undo/circumvent it because it's harmful. The way I see it, the platform is shooting itself in the foot, repeatedly, but it gets angry at us when we suggest to take away its gun.


I'm just saying it's more like taking away its motorbike - people use them because they're fun and easy to get around with, but they'll probably hurt you some day - and it's not our job to ban motorbikes but to design roads that are safe for them.

Metaphor explosion.
 
Lastly - I am not really sure about how harmony proxies work, but it sounds like we're told after they change not before they change? If not, that would certainly be a neater solution than what we're doing for JSON (saving function references on load), but I can see it getting messy with prototypes.

I guess the best you can do is before you hand the context over to the extension, create a copy of all the library functions/objects you care about. Then you can restore that as necessary when control comes back to your code. It should be possible to package this behavior relatively nicely; maybe a custom alternate global object helps (so you wouldn't have to restore anything, you'd just mechanically s/Array/original_global.Array/g in your code). You don't need proxies for this, nor any other way to observe what the extension is mucking with.

I tried that sort of thing actually, but ultimately it doesn't seem possible to prevent our code from doing things like "var schemas = []" instead of "var schemas = original_global.Array()" - and it's unrealistic for us to remember to do that everywhere. It's how I arrived at running in a separate context being the only way to do this that protects us against ourselves. There's also a chicken-egg problem where you can override Function.prototype.call and we're totally screwed (umm, hopefully extensions wouldn't do that).

Joshua Bell

unread,
Mar 15, 2013, 4:06:08 PM3/15/13
to v8-u...@googlegroups.com
*If* you know your scripts will run first and *if* it is acceptable to deny external scripts the ability to modify built-ins, you could consider using SES. https://code.google.com/p/es-lab/wiki/SecureEcmaScript



--

Benjamin Kalman

unread,
Mar 15, 2013, 4:07:35 PM3/15/13
to v8-u...@googlegroups.com

On Fri, Mar 15, 2013 at 1:06 PM, Joshua Bell <jsb...@chromium.org> wrote:
*If* you know your scripts will run first and *if* it is acceptable to deny external scripts the ability to modify built-ins, you could consider using SES. https://code.google.com/p/es-lab/wiki/SecureEcmaScript

Neither of those is true unfortunately, though we can run some code first if necessary.

Yang Guo

unread,
Mar 20, 2013, 4:28:24 AM3/20/13
to v8-u...@googlegroups.com
Sounds like defining your own Array.prototype.forEach would solve the problem.

Yang
Reply all
Reply to author
Forward
0 new messages