Ticket #137 (new defect)

Opened 4 months ago

Last modified 1 month ago

Script works in FF2 + GM 0.8.20080609.0 but fails with FF3 + same GM

Reported by: jtra..@dragoncat.net Assigned to:
Priority: major Milestone:
Version: 0.8.20080609.0 Keywords: FF3
Cc: arantius@gmail.com

Description

I have a script which makes heavy use of GM_getValue, GM_setValue, and GM_xmlhttpRequest.

This script functions fine under GM 0.8.20080609.0 and firefox 2. However, when I run the same script under the same version of Greasemonkey on Firefox3, I get the following error.

I am working on whittling down the script to a bare-minimum case since it's a rather large script. The fundamental of the script is that it checks credentials against another service/site that I run and if they are authenticated against that service pulls down an updated version of the script (if necessary) and stores it in a preference. It then evals the javascript code stored in this preference (or, if they are not authenticated, the javascript code returned from the authentication call).

Under FF3, this now generates a "Greasemonkey access violation: unsafeWindow cannot call GM_getValue" (and similar for setValue and xmlhttpRequest).

The key to note here is that the fundamental difference is that the same version of greasemonkey WORKED with FF2 in this exact same script. The only thing that changed was upgrading to FF3.

Now it is quite possible that I am abusing something here, and if so, I'd love help figuring out how to work around it, but I do need to be able to validate against this external services before executing the script or executing/updating the script returned from the service if need be and I need to be able to call GM_get/set functions from inside that block of code. I will put up a simplified version of my script which demonstrates this error as soon as possible.

Attachments

gm_bug.tar.gz (1.4 kB) - added by jtra..@dragoncat.net on 06/18/08 18:23:19.
test files for reproducing the problem.
gm_unsafe_hack.diff (5.6 kB) - added by jtra..@dragoncat.net on 06/25/08 19:50:45.
gm_unsafe_hack.fixed.diff (5.6 kB) - added by jtra..@dragoncat.net on 06/25/08 22:52:31.
eval_workaround.user.js (0.6 kB) - added by d..@phiffer.org on 06/30/08 12:20:25.
Demonstrates how to circumvent new security constraints in FF3

Change History

(follow-up: ↓ 8 ) 06/18/08 13:40:56 changed by jtra..@dragoncat.net

Okay.. simple demoscript is available. I put it up at http://www.dragoncat.net/~jtraub/gm_bug

There is a link on that page to the script and the page explains what you should see once you install that script and rehit that page.

Hopefully someone can help me resolve this.

Once again the key capability I need is to be able to call GM_*Value and GM_xmlhttpRequest from inside the code that I am pulling down from my server based on their authorization status. It is not possible nor acceptable to put all of the code into the main script because the pieces that get loaded for the user depend on permissions they have with this external data source and exposing functionality/code they don't have permission to use/run is not acceptable for my use case.

06/18/08 17:30:40 changed by aranti..@gmail.com

Thanks, it would be best to attach the script to avoid server downage / broken links / etc.

06/18/08 18:19:26 changed by jtra..@dragoncat.net

Didn't realize that would be useful in this case since it fundamentally relies on having the script load and then execute another piece of JS off of the server. I can certainly tar up the entire set of files (2 JS, 1 PHP, 1 HTML) and stick it up here as a tarball though, and will go do so.

06/18/08 18:23:19 changed by jtra..@dragoncat.net

  • attachment gm_bug.tar.gz added.

test files for reproducing the problem.

06/18/08 18:42:26 changed by aranti..@gmail.com

To be honest I hadn't even read that far when I commented, I fail for assuming the worst. I'll get to this sooner or later, but I'm trying to clean up the backlog of open tickets to make it easier to prioritize those that remain.

06/18/08 18:51:39 changed by jtra..@dragoncat.net

No worries.. I'm a dev myself, and occasionally run with those same 'blinders' on. I'm honestly hoping this is something easy to fix :)

06/18/08 19:26:27 changed by aranti..@gmail.com

  • keywords set to FF3.

06/19/08 01:04:27 changed by anonymous

I'm having exactly the same Problem. As far as I found out the problem is located in the eval-Function. It doesn't seem to matter from where the string to eval comes from.

eval('GM_getValue("test", "TESTING");');

Works with FF2, but throws an error with FF3.

(in reply to: ↑ 1 ) 06/20/08 02:04:22 changed by anonymous

Just to say that I notice that the first GM_getValue in my script is good, returning the good value, but not the others. I don't know why. The first (in the variable definition section) is out of a function , not the others.

06/20/08 12:58:39 changed by d..@phiffer.org

Is this possibly related to this bug?

06/20/08 13:07:36 changed by aranti..@gmail.com

  • cc set to aranti..@gmail.com.

The error that you're seeing is a direct result of checks to ensure that privileged GM functions do not leak to content pages. You can see that exact string here. That makes me really doubt it's related to that specific mozilla bug.

This is done by evaluating the call stack. If it doesn't contain exclusively things that are part of the GM sandbox, it triggers. I'm not at all confident that eval() was taken into consideration when this was written. I'll try to take a look at the internals when eval is used, see if I can distinguish it from the information available on that stack, and most importantly see if I can notice a difference in behavior between FF2 and FF3.

06/20/08 13:14:08 changed by aranti..@gmail.com

Confirmed a simpler test case:

eval("GM_getValue('foo');");

Same error:

Error: Greasemonkey access violation: unsafeWindow cannot call GM_getValue.

In FF3, but not FF2.

06/21/08 08:53:57 changed by aranti..@gmail.com

As I suspected. Based on the information that Components.stack makes available, code run from an eval() is indistinguishable from code that came from content space.

I don't think we can support this securely.


If I add this change:

Index: components/greasemonkey.js
===================================================================
--- components/greasemonkey.js  (revision 735)
+++ components/greasemonkey.js  (working copy..@@ -21,6 +21,7 @@
   var stack = Components.stack;
 
   do {
+       dump(stack+'\n');
     // Valid stack frames for GM api calls are: native and js when coming from
     // chrome:// URLs and the greasemonkey.js component's file:// URL.
     if (2 == stack.language) ..@@ -32,7 +33,7 @@
           stack.filename.substr(0, 6) != "chrome") {
         GM_logError(new Error("Greasemonkey access violation: unsafeWindow " +
                     "cannot call " + apiName + "."));
-        return false;
+        dump('Would have returned false here!\n');
       }
     }

The entire stack trace (with the 1-line test case I posted above) running in FF2 (minus profile-specific bits) looks like:

  1. JS frame :: file:///C:/.../components/greasemonkey.js :: GM_apiLeakCheck :: line 20
  2. JS frame :: chrome://greasemonkey/content/miscapis.js :: anonymous :: line 18
  3. native frame :: <unknown filename> :: <TOP_LEVEL> :: line 0
  4. JS frame :: chrome://greasemonkey/content/utils.js :: anonymous :: line 39
  5. JS frame :: file:///C:/.../components/greasemonkey.js :: anonymous :: line 350
  6. JS frame :: file:///C:/.../components/greasemonkey.js :: anonymous :: line 350
  7. JS frame :: file:///C:/.../components/greasemonkey.js :: <TOP_LEVEL> :: line 347

While the same in FF3 looks like:

  1. JS frame :: file:///C:/.../components/greasemonkey.js :: GM_apiLeakCheck :: line 21
  2. JS frame :: chrome://greasemonkey/content/miscapis.js :: anonymous :: line 18
  3. JS frame :: chrome://greasemonkey/content/utils.js :: anonymous :: line 39
  4. JS frame :: http://www.example.com/ :: anonymous :: line 0
    Would have returned false here!
  5. JS frame :: file:///C:/.../components/greasemonkey.js :: anonymous :: line 351
  6. JS frame :: file:///C:/.../components/greasemonkey.js :: <TOP_LEVEL> :: line 348

06/21/08 09:29:20 changed by jtra..@dragoncat.net

But obviously there is something different between FF2 and FF3 that is causing that. Is there something they changed that we can get them to change back? not being able to use eval inside of GM scripts is horrendously broken :(

06/21/08 10:48:43 changed by d..@phiffer.org

not being able to use eval inside of GM scripts is horrendously broken

It's not that you can't use eval, but that the GM_* functions are off-limits. Actually I think only some of them are off-limits: GM_*etValue and GM_xmlhttpRequest. This is still a huge setback for us at ShiftSpace? since we've built a lot of our infrastructure around eval'ing JS and expecting those functions to remain available.

06/21/08 22:05:36 changed by jtra..@dragoncat.net

Sorry dan, you're right, but I meant 'not being able to eval code which then called specific GM_*'. The entire script (and security model) that I have is built around exactly that functionality. Could we get a 'DANGER.DANGER.DANGER.set.this.at.your.own.peril' greasemonkey option to turn off this security check so that at least we can continue using our scripts under GM + FF3 until there is a better fix??

06/24/08 11:04:37 changed by d..@phiffer.org

Just poking this to see if anyone is still looking at it. We can't release a FF3-compatible version of ShiftSpace? until this is fixed. Is there any way I can help push this forward? Is there somebody on the Mozilla side of things who might be receptive to inquiries about how to get this fixed?

06/24/08 11:13:29 changed by aranti..@gmail.com

IMHO if you have your own app that depends critically on this firefox addon, you should probably make your own extension. You're then free to define the internal workings to satisfy your own security scheme.

I don't currently know of any way to support calling privileged APIs from eval() that wouldn't also expose them to content scripts. Progress here is not likely until such a thing is found. If you really want to push it forward, learning and understanding why we check for this -dev thread, then figure out how to keep this security check in, but tell the difference between eval in a script, and anything out in the wild on a page.

06/24/08 13:59:14 changed by jtra..@dragoncat.net

Ok.. something here just doesn't make sense to me.

This worked in FF2 with the newest version of the GM plugin. Obviously something changed in FF3 about how it handles eval which caused this to break. Why can the Mozilla devs not help here? either by giving you some way to determine that it was eval, etc. Surely you cannot be serious in thinking that at least two people should create their *own* versions of GM which do everything that GM does except bypassing this security check! that's about as absurd a suggestion as I've ever heard from any developer.

Reading through that thread you mentioned, it sounded as if you had some ideas which would have been better but required help from the Mozilla devs. Yet, you didn't pursue them, instead taking a route which broke a legitimate use case rather than find a solution which worked even if it required some help from the moz devs. I'm not quite sure why you made that choice.

I also requested and suggested above a very ominous sounding user config preference which would let us disable this security check. Is there some reason you are unwilling to put such a mechanism in place with it named suitably such that noone would set it without understanding the issues? Surely that is a saner course than myself and Dan both creating an independent competitor to greasemonkey?

06/24/08 16:23:26 changed by jtra..@dragoncat.net

arantius: looking back up at the stack trace, it seems like what changed is that (for some reason) eval seems to always consider itself running in the context of the page *even* when what called eval was the GM user script. It seems that the context there should have been the GM script itself, not the website the script was running over.

If that were the case, then it would seem that it should work again.

That seems to be the root of the problem, am I right? That seems like the FF3 devs goofed in that regard, or am I somehow wrong here?

06/24/08 23:27:11 changed by jtra..@dragoncat.net

arantius: how about something like this as a potential solution? Create another GM_* api (which is sanity checked the same way as the existing ones meaning that it could only be called from a GM user script or chrome) called GM_eval(). Inside of GM_eval, set a global which lets the api check know that you are inside of GM_eval and then call eval on the code passed in via the string arg. GM_apiLeakCheck could then check this global and thus know if you were eval'ing in a reasonable context (ie, something where the code to eval came from something in the greasemonkey script itself).

Would that satisfy your concerns about the security hole (seems like it should since you couldn't call GM_eval except in a context that you could call GM_setValue, etc). It seems like it would allow me to to do what I needed to do since I could call GM_eval (which would then disable the api check) from the top-level GM script rather than calling eval which breaks things, and possibly also offer Dan a way to make his script work as well.

I am willing to try and write this up as a patch if you like, but I know there are nuances that I don't understand and I'd want to make sure that we couldn't somehow set the semaphore for whether the leak check should be performed from inside the GM script, etc and I don't quite know how that would be done.

06/24/08 23:29:57 changed by jtra..@dragoncat.net

basically in psuedocode

function GM_eval(string) {

ignoreApiCheck = 1; eval(string); ignoreApiCheck = 0;

}

and then in GM_apiLeakCheck if ignoreApiCheck == true return true; else

// do the check as currently written

06/25/08 19:41:01 changed by aranti..@gmail.com

The problem that leakcheck solves is that it's possible, given a poorly written script (and unfortunately, many of them are), it's possible for a content page to get access to privileged GM_* functions.

All your suggested solution does is turn off this check. The content script could still call this, new, GM_eval() and thus get access to the privileged actions. In other words, this is a problem and not a solution.

As far as the eval identification goes, it's more likely that this is a fix than a problem. The sandbox that GM scripts run in is intentionally set up to "look like" the content page it is running on. This is how document.getElementById() and everything else works. It's probably a fix, a true enhancement, that has this connection continuing down inside eval.

06/25/08 19:50:23 changed by jtra..@dragoncat.net

I really do understand this.. Unfortunately this is putting both myself and Dan between effectively a rock and a hard place. I actually figured out that the GM_eval() idea wouldn't work anyway since anything which used a callback would return from the eval and then the callback would fire and thus the callback called from inside the eval still wouldn't have access to the GM_* functions which is what I actually need.

I have another suggestion which I've coded as a patch and verified that it a) works in my use case and b) will have no effect except on scripts specifically modified to ignore the check.

What I did was introduce a UserScript? directive, @unsafe. If this is set to 1 (or any value which doesn't evaluate to 0/false) then the script will be marked as unsafe and the apicheck will be ignored. What I didn't do was put in extra dialogs when installing an unsafe marked script or highlight those scripts in the script list, but if you accept this as an idea, then it would allow myself and Dan to mark our scripts as unsafe and have them still function. I fully accept that this is an atrocious hack, but I honestly cannot see any other way to come up with a solution which allows the functionality I need; since I really don't believe creating a second type of greasemonkey/user script is desirable. I also beat my head around trying to find some other way to do what I needed to do without eval, but I really couldn't come up with one that would meet the needs I have (of being able to pull down script parts on the fly depending on the persons permissions on my external site) without using eval in some way, shape or form.

Please take a look over my patch and see if you can see anything that I'm doing which is utterly brain-damaged (other than the fact that I'm deliberately allowing a script author to circumvent this security feature).

06/25/08 19:50:45 changed by jtra..@dragoncat.net

  • attachment gm_unsafe_hack.diff added.

06/25/08 19:53:27 changed by jtra..@dragoncat.net

BTW, if you want to try this out, I have the modified greasemonkey at http://www.dragoncat.net/~jtraub/gm_bug/greasemonkey-0.8.20080625.0.xpi and a second version of the test script which adds the // @unsafe 1 to the userscript header at http://www.dragoncat.net/~jtraub/gm_bug/test2.user.js

If you install both test.user.js and test2.user.js you'll see that one of them still returns undefined (the original) and the other returns val2 (the new script with unsafe 1). This was just to verify that I wasn't bleeding over into another sandbox somehow when I put an unsafe script on the page.

06/25/08 22:45:17 changed by jtra..@dragoncat.net

okay.. somehow it seems that in continued use it is actually not obeying the unsafe/safe distinction at all, so please ignore that previous patch until I figure out what braindamage I did which is causing the code I wrote to cause all uses to be unsafe. Once I fix that I'll re-upload the patch.

06/25/08 22:52:31 changed by jtra..@dragoncat.net

  • attachment gm_unsafe_hack.fixed.diff added.

06/25/08 22:54:18 changed by jtra..@dragoncat.net

found/fixed.. I forgot a parseInt call when I made a change earlier. Now it works again as expected -- scripts with @unsafe 1 (or any non-zero integer value) will avoid the api check.

06/30/08 12:20:25 changed by d..@phiffer.org

  • attachment eval_workaround.user.js added.

Demonstrates how to circumvent new security constraints in FF3

(follow-up: ↓ 28 ) 06/30/08 12:22:45 changed by d..@phiffer.org

Thanks to some clever hackery by ShiftSpace? developer David Nolen, we now have a way to work around the Firefox 3 security constraints that make eval() and GM_* functions not play nice together. Take a look at the eval_workaround.user.js attachment which shows a very simple test case.

(in reply to: ↑ 27 ) 06/30/08 17:54:09 changed by david.nol..@gmail.com

Replying to d..@phiffer.org:

Thanks to some clever hackery by ShiftSpace? developer David Nolen, we now have a way to work around the Firefox 3 security constraints that make eval() and GM_* functions not play nice together. Take a look at the eval_workaround.user.js attachment which shows a very simple test case.

Note that this won't work for the GM_getValue case where you need the value immediately in order to set a var. For this you'll need a callback of some sort.

(follow-up: ↓ 30 ) 08/24/08 17:41:40 changed by John Alexander

The suggested work-around seems to solve the problem of calling functions defined in the GM context from evaled code, but what about the other way around?

E.g.

var s = "function f(){ GM_log(GM_getValue('foo')); }";
f();

That's the typical use case I've seen. E.g. for loading GM-function dynamically (e.g. from a private repository via GM_xmlhttpRequest).

f.safeCall(); the "timeout hack" isn't working in this case either.

Perhaps I missed something in the workaround description and this use case is possible, if so I'd really like to get that pointed out.

(in reply to: ↑ 29 ) 08/24/08 21:19:40 changed by david.nol..@gmail.com

The scenario you are describing is just not possible. In our project we consider all GM_ functions to be "primitives." So we wrap them. If an eval'ed piece of code needs to access GM functionality it will safeCall a GM_ function wrapper. In the case of GM_getValue we have added a method called safeCallWithResult to the Function prototype.

GM context code

function OurGetValue(key, default)
{
    GM_getValue(key, default);
}

eval'ed code

function f() 
{
    OurGetValue.safeCallWithResult('foo', null, function(result) {
        console.log(result); // no GM_log because console.log works and GM_log would have to be safeCall'ed as well
    });
}

David


Add/Change #137 (Script works in FF2 + GM 0.8.20080609.0 but fails with FF3 + same GM)