Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

through GM_apiLeakCheck of GM_* api from keyboard events via an iframe #1494

teramako opened this Issue Jan 5, 2012 · 14 comments


None yet
3 participants

teramako commented Jan 5, 2012

When an UserScript listen keyboard events and then do some operation,
normally cannot execute GM_* apis from content's created event, but events via javascript URL of iframe.src can do.

Test code is here: https://gist.github.com/1565506

UserScript auhors need validate either aEvent.isTrusted or not by oneself ?

teramako commented Jan 6, 2012

fixed the title (s/though/through/)

teramako commented Jan 7, 2012

I reported to Mozilla Bug 716262


arantius commented Jan 25, 2012


Repro steps:

Install the .user.js in the gist, edit it to

// @include        http://fiddle.jshell.net/_display/

Then go to http://jsfiddle.net/nuBgA/6/ .

This is a work around to bug #1001 . And/or a security hole.


Ventero commented Jan 25, 2014

After investigating a bit, it looks like the situation has changed quite a bit by now. It looks like any page-generated event passes GM_apiLeakCheck when it isn't explicitly handled by the content scope.

To give an example: Assuming GM_openInTab leaked into the content scope somehow (which nowadays seems to be only possible by having the script explicitly assign unsafeWindow.GM_openInTab = GM_openInTab or something like unsafeWindow.sandbox = this - the old trick of waiting for a content function to be called and then walking up the function.caller chain doesn't seem to work anymore, as the .caller that would cross into the sandbox seems to be null). Then the following passes the API leak check:

var div = document.createElement("div");
div.addEventListener("click", sandbox.GM_openInTab, false);

but the following doesn't (due to appearance of the content page in the call stack):

var div = document.createElement("div");
div.addEventListener("click"; function(){sandbox.GM_openInTab("foo");}, false);

Since the argument received by GM_openInTab in the first case is an Xray-wrapped event, I don't think there's any way to pass an (effectively) arbitray argument to the API function by using .valueOf trickery or something similar.

So while this seems to make it possible to circumvent the API leak check, it's only possible to call the API functions with a very limited set of arguments. This of course is far from ideal, but at least it shouldn't be a huge security issue. However, I have no idea how we could prevent this.

But on the bright side, it looks like #1001 is finally fixed?

This was tested with Mozilla/5.0 (X11; Linux x86_64; rv:27.0) Gecko/20100101 Firefox/27.0 and here's a gist containing the example content page and userscript.


arantius commented Jan 26, 2014

Since the argument received by GM_openInTab in the first case is an Xray-wrapped event, I don't think there's any way to pass an (effectively) arbitray argument to the API function by using .valueOf trickery or something similar.

Interesting findings! I can't think of an GM_ API that would legitimately take an Event as its first argument, so maybe we just block those too?


Ventero commented Jan 26, 2014

Maybe just block all calls containing any Xray wrapped argument, as those are clearly coming from outside the sandbox.

arantius added a commit to arantius/greasemonkey that referenced this issue Jan 29, 2014


arantius commented Jan 29, 2014

I couldn't find a good way to check for xray-wrapped-ness. Please take a look at the commit I pushed for this.


Ventero commented Jan 29, 2014

Your check should definitely work, but there might be false positives (then again, I can't think of a GM-API function where an object with a wrappedJSObject property would show up in a legitimate use of that API). To make this check a bit more strict, something like XPCNativeWrapper.unwrap(arg) == arg.wrappedJSObject could be used.

What's confusing me a bit is the fact that in the second test case from the gist I posted earlier, the argument is not XrayWrapped - I always thought that any object passing through a sandbox boundary would be wrapped. But since that case is caught by the stack check, this shouldn't be an issue.


Ventero commented Jan 29, 2014

Mh, nevermind that. XPCNativeWrapper.unwrap(arg) == arg.wrappedJSObject still triggers the same false positives.


Ventero commented Jan 29, 2014

Sorry, disregard the last message. XPCNativeWrapper.unwrap(arg) == arg.wrappedJSObject definitely is a more strict check. When passed an object like {wrappedJSObject: "foo"}, this check returns false, whereas a simple check for arg.wrappedJSObject passes. Not sure about something like var k = {}; k.wrappedJSObject = k; though. In my understanding, XPCNativeWrapper.unwrap(k) should just return k, so that the check would pass. But tests show that it doesn't - which I don't quite understand.

(And I'm terribly sorry for the bugspam)


arantius commented Jan 29, 2014

Don't be sorry. I would definitely prefer a better test. I just don't know for sure how to do it yet.


Ventero commented Jan 29, 2014

There's an (undocumented) Components.utils.isXrayWrapper, which seems to be exactly what we want. But I'm not sure if it's a good idea to rely on an undocumented function. So maybe something like the following?

if(Cu.isXrayWrapper && Cu.isXrayWrapper(arg) ||
    XPCNativeWrapper.unwrap(arg) == arg.wrappedJSObject) {
  throw ...;
  return false; 

@arantius arantius modified the milestones: 1.16, 1.15 Feb 11, 2014

@arantius arantius modified the milestones: 1.17, 1.16 Apr 8, 2014

@arantius arantius modified the milestones: 2.2, 2.1 Jun 20, 2014

@arantius arantius modified the milestones: 2.3, 2.4 Sep 21, 2014

@arantius arantius modified the milestones: 2.4, 2.5 Oct 29, 2014


arantius commented Oct 30, 2014

Is this obsolete since expanded principal sandboxes? Leaving in this milestone to check.


arantius commented Apr 2, 2015

Well, yes. This is obsolete because there is no more GM_apiLeakCheck(). After I edit the several year old test case to run again, the plain sendEvent() works -- it generates a click event, and the click event is handled by the script. That seems pretty expected.

I've always held that Greasemonkey is for adding functionality and for fixing broken sites. But never for expressly violating the wishes of the site owner. If you can't trust the site owner not to generate events, and you react to those events in dangerous ways, then you shouldn't be doing that.

@arantius arantius closed this Apr 2, 2015

@arantius arantius removed this from the 3.2 milestone Apr 2, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment