Restrict the schemes allowed for ChromeUtils.import to resource:// and chrome://
Categories
(Core :: XPConnect, task)
Tracking
()
People
(Reporter: freddy, Assigned: freddy)
References
Details
(Keywords: sec-want, Whiteboard: [post-critsmash-triage][adv-main101-])
Attachments
(1 file, 4 obsolete files)
We're thinking about restricting script loading in the parent process to things that are part of the build. This bug will find out if it's feasible to require ChromeUtils.import() to always take a string literal.
| Assignee | ||
Comment 1•6 years ago
|
||
FInds 37 violations. See spreadsheet at https://docs.google.com/spreadsheets/d/1-nJsx-KIsLyTzvu4jJJ8Oc21XA6xH_Wp0FX52TnvRfk/edit#gid=0 for more.
| Assignee | ||
Comment 2•6 years ago
|
||
Comment 3•6 years ago
|
||
I'm going to mark this sec-want under the assumption that this is more of a hardening measure than something that you expect will find a problem.
| Assignee | ||
Comment 4•6 years ago
|
||
Yes, sec-want is correct. This is not a security bug and I don't expect to find security bugs with this. It's hardening.
| Assignee | ||
Updated•6 years ago
|
| Assignee | ||
Updated•6 years ago
|
| Assignee | ||
Comment 5•6 years ago
|
||
Unassigning this hardening bug. I don't think I will be able to continue working on this.
Comment 6•6 years ago
|
||
Tom, Do you think we should add this to our todo list?
thanks
Comment 7•6 years ago
|
||
I'm not sure. The work I'm doing in Bug 1562221 will prevent this at runtime in the browser. So you can use a variable name but it will need to pass that runtime inspection. On the flip side; a "data:" URI can be a string literal and pass the linter but not pass runtime inspection.
And a question is from a workflow perspective: do developers lint their code before they run it and see if it crashes? (I suspect they do not.)
So I'm inclined not to put in the work to clean up the existing violations just from a workload perspective...
Comment 8•6 years ago
|
||
(In reply to Tom Ritter [:tjr] (needinfo for responses to sec-[approval/ratings/advisories]) from comment #7)
And a question is from a workflow perspective: do developers lint their code before they run it and see if it crashes? (I suspect they do not.)
They usually don't but as it would show at review phase, not a big deal
| Assignee | ||
Comment 9•4 years ago
|
||
@Standard8 (and others): What do you think? Is this still useful as a companion and early-warning to help folks catch during development than rather at runtime?
If so, the patch still seems to work but has lots of known failures on try, where we'd need to make sure that automation only complains about new failures (Unfortunately, I forgot how that exactly works for known linter violations in eslint).
I'm also happy to spend some time on this but just equally happy mark this WONTFIX if you think it's not.
Comment 10•4 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #9)
@Standard8 (and others): What do you think? Is this still useful as a companion and early-warning to help folks catch during development than rather at runtime?
If so, the patch still seems to work but has lots of known failures on try, where we'd need to make sure that automation only complains about new failures (Unfortunately, I forgot how that exactly works for known linter violations in eslint).
You can use a default level of "warning". Or we could add annotations for the existing violations (there's only about 2 dozen, per the try job, that's not that bad...)
I'm also happy to spend some time on this but just equally happy mark this WONTFIX if you think it's not.
I think we should clamp down on the dynamic checks and make them crash. I don't think the work in bug 1562221 is quite the same - I think for ChromeUtils.import we should feasibly be able to restrict to chrome and resource URLs. But of course I haven't tried that...
Comment 11•4 years ago
|
||
(In reply to :Gijs (he/him) from comment #10)
(In reply to Frederik Braun [:freddy] from comment #9)
@Standard8 (and others): What do you think? Is this still useful as a companion and early-warning to help folks catch during development than rather at runtime?
If so, the patch still seems to work but has lots of known failures on try, where we'd need to make sure that automation only complains about new failures (Unfortunately, I forgot how that exactly works for known linter violations in eslint).You can use a default level of "warning". Or we could add annotations for the existing violations (there's only about 2 dozen, per the try job, that's not that bad...)
I'm not sure "warn" is ideal for this case - I think this should be either it is fixed or it is explicitly allowed.
If we add annotations for the existing violations, then we should ensure that those existing violations are acceptable when we do that, and if not we should fix them. I don't think we should remain in a state where we're not sure about the existing violations in the tree - there aren't lots, so this doesn't seem like too much work.
I'm also happy to spend some time on this but just equally happy mark this WONTFIX if you think it's not.
I think we should clamp down on the dynamic checks and make them crash. I don't think the work in bug 1562221 is quite the same - I think for ChromeUtils.import we should feasibly be able to restrict to
chromeandresourceURLs. But of course I haven't tried that...
This sounds like it may be the better option overall as it would ensure we catch all cases (e.g. there is some code that isn't linted).
| Assignee | ||
Comment 12•4 years ago
|
||
Yeah, I agree that it would be more desirable to do something at runtime rather than linting.
(In reply to Mark Banner (:standard8) from comment #11)
(In reply to :Gijs (he/him) from comment #10)
(In reply to Frederik Braun [:freddy] from comment #9)
I think we should clamp down on the dynamic checks and make them crash. I don't think the work in bug 1562221 is quite the same - I think for ChromeUtils.import we should feasibly be able to restrict tochromeandresourceURLs. But of course I haven't tried that...This sounds like it may be the better option overall as it would ensure we catch all cases (e.g. there is some code that isn't linted).
I gave this a try and my first test run about file: URIs which we seem to need for XPIDatabase.jsm::getResourceURI. I could imagine just giving in and allowing file:. It does weaken the "part of the build" precondition that chrome/resource URIs had, but still confines the target resource to local data, which is much better than "anything goes".
I'll try again with file allowed and will repurpose this bug if it works out. The latest try push is https://treeherder.mozilla.org/#/jobs?repo=try&revision=c98917b439539e23a2e8f5fee3d6a3ee7e73abd1.
As an aside, I have also noticed that ChromeUtils.import() is (obviously) just one of many ways to load and execute privileged JavaScript. With this caveat, I might also abandon this bug completely and find a lower-level control instead.
Comment 13•4 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #12)
Yeah, I agree that it would be more desirable to do something at runtime rather than linting.
(In reply to Mark Banner (:standard8) from comment #11)
(In reply to :Gijs (he/him) from comment #10)
(In reply to Frederik Braun [:freddy] from comment #9)
I think we should clamp down on the dynamic checks and make them crash. I don't think the work in bug 1562221 is quite the same - I think for ChromeUtils.import we should feasibly be able to restrict tochromeandresourceURLs. But of course I haven't tried that...This sounds like it may be the better option overall as it would ensure we catch all cases (e.g. there is some code that isn't linted).
I gave this a try and my first test run about
file:URIs which we seem to need forXPIDatabase.jsm::getResourceURI.
Uhh... I am confused - the callers of that method seem to care about images. Why are we ending up in ChromeUtils.import with those URIs?
| Assignee | ||
Comment 14•4 years ago
|
||
Ouch. I tracked this to the wrong function! The test in docshell/test/unit/test_allowJavascript.js has its own getResourceuRI that just returns Services.io.newFileURI(do_get_file(file)).spec;.
Thank you, Gijs.
Tracing this again, all I can find are test-only occurrences:
filein docshell/test/unit/test_allowJavascript.jsfileindom/indexedDB/test/unit/test_globalObjects_other.jsfileinjs/xpconnect/tests/unit/test_bug408412.jsjar:in js/xpconnect/tests/unit/test_import.js
I suppose one could continue restricting to "chrome, resource, jar, file" first and then iterate to either remove the usage of file & jar URLs or allow them only when under test.
I'll do another test :)
| Assignee | ||
Comment 15•4 years ago
|
||
It's green on try.
TLDR: We could change ChromeUtils.import() to only allow URLs of schemes file, jar, chrome, resource.
File and jar are required for tests and could be deprecated iteratively, would require some more work.
Tom, what's your take? Proceed or invest time elsewhere?
Comment 16•4 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #14)
Ouch. I tracked this to the wrong function! The test in docshell/test/unit/test_allowJavascript.js has its own
getResourceuRIthat just returnsServices.io.newFileURI(do_get_file(file)).spec;.Thank you, Gijs.
Tracing this again, all I can find are test-only occurrences:
filein docshell/test/unit/test_allowJavascript.jsfileindom/indexedDB/test/unit/test_globalObjects_other.jsfileinjs/xpconnect/tests/unit/test_bug408412.jsjar:in js/xpconnect/tests/unit/test_import.jsI suppose one could continue restricting to "chrome, resource, jar, file" first and then iterate to either remove the usage of file & jar URLs or allow them only when under test.
I'll do another test :)
I think for all of these the solution is likely to end up being putting the requested modules in a TESTING_JS_MODULES in the moz.build file and then using a resource or chrome URI to import it. It doesn't look super-onerous to do it here but if you want to split it up that also wfm.
The only weird case is that jar: one (test_import.js) - the comments are a bit confusing; it looks like it was changed in part 5 of bug 1383215 to expect things to throw instead of work. If you made the changes here throw an exception instead of crash then we would probably be OK even without changing that test (though it'd be nice if we could update the code comment to make it clearer what is being tested)? ;-)
| Assignee | ||
Comment 17•4 years ago
|
||
With this change, the function will throw an exception when the
supplied URL does not have the scheme resource, chrome or file.
Updated•4 years ago
|
Comment 18•4 years ago
|
||
It looks like you've settled on a dynamic rather than linting solution, so I'm going to move this to XPConnect.
Updated•4 years ago
|
Updated•4 years ago
|
Updated•4 years ago
|
Comment 19•4 years ago
|
||
Seems it's about done, which SGTM. It seems like we should add this to our exploit mitigation list.
Comment 20•4 years ago
|
||
Please file a followup bug about eliminating the jar and file schemes, and include or refer to your analysis in comment 14 in that bug.
Please also file a bug in Thunderbird::Upstream Synchronization. Thunderbird does some weird stuff, so who knows what it might break. Hopefully nothing, but it would be better to give them a heads up, as the failures might be mysterious without digging in.
Also, please update the summary of this bug to reflect what it is about now. Thanks.
Comment 21•4 years ago
|
||
While looking at your test, I noticed that I think we may already reject everything except file, jar and resource URIs.
In my local testing, it looks like the test passes even without your patch, while printing out this message: exception (expected) for unknown schemes: [Exception... "Component does not have requested interface" nsresult: "0x80004002 (NS_NOINTERFACE)" location: "JS frame :: [...]test_import.js :: run_test :: line 96" data: no]
Also, you can rewrite the test to use Assert.throws. There's an example of it earlier in the file.
I think this does more like what you want:
var scope4 = {};
var dataPath = "data:text/javascript,var a = {a:1}";
Assert.throws(() => ChromeUtils.import(dataPath, scope4),
/SecurityError/);
This fails without your patch and passes with it. However, I looked into what that NS_NOINTERFACE error actually means, and it looks like it comes from this line in Import: baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv);
What this means is that the import code tries to resolve the URI, then attempts to QI it to either nsIJARURI or nsIFileURL. A javascript URI isn't either, so we reject it. ResolveURI only changes resource and chrome URIs, so I think in practice we already only allow jar, file, resource and chrome URIs. Well, maybe not jar because you said we reject them already somehow?
Anyways, we should probably get a better handle on whether this patch actually does anything before we land it, though I guess maybe it is good to have the restrictions more explicitly checked.
Comment 22•4 years ago
|
||
Never mind what I said in comment 20, as we should figure out what the status is before filing a Thunderbird or followup bug.
| Assignee | ||
Comment 23•4 years ago
|
||
You're right. Rereading the code just confirms what you say in comment 21. The implementation only works for the interfaces of jar and file URLs. In essence, the only< remaining work is then to merely add a test for the types of URLs we hope never to support.
Comment 24•4 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #23)
You're right. Rereading the code just confirms what you say in comment 21. The implementation only works for the interfaces of jar and file URLs. In essence, the only< remaining work is then to merely add a test for the types of URLs we hope never to support.
So, fwiw, I disagree a bit with this. In particular, I think there is value in restricting this so that we can only import chrome and resource URIs, not other jar URLs or file URLs, because it restricts ChromeUtils.import to the stuff that shipped with the browser. This would make it harder to run local escalation attacks where you'd need/want to load external files from disk. You can dynamically add mappings for chrome/resource URLs if you know how to, and if whatever exploit path it is allows you to run the relevant code -- so how big a hurdle that is probably depends on the rest of the exploit chain. But it still seems reasonable to encode our assumptions (ie that ChromeUtils.import only gets used for in-browser code) into code here.
Comment 25•4 years ago
|
||
Actually, come to think of it, I would expect that you can't add chrome/resource mappings from the child process, but you may be able to load arbitrary file URLs from, say, the temp or downloads directory of the user's machine...
Comment 26•4 years ago
|
||
I agree that we would ideally only allow chrome and resource URIs, but the current patch does not do that, as it continues to allow file URIs. The question is, is it worth landing the current patch as-is, or should we wait until the extra work is done to also restrict file URIs?
FWIW, I modified my test case from comment 21 so that dataPath is a file URI referring to a file on my computer, and it did attempt to load it. Of course, outside of an xpcshell test, you'd be limited to where you could load a file from, as you said.
Comment 27•4 years ago
|
||
I've updated the summary to reflect what the patch is currently doing.
Comment 28•4 years ago
|
||
I can write up a patch to fix the 4 tests (comment 14) that need fixing to not import file.
Comment 29•4 years ago
|
||
Thanks, Gijs.
Comment 30•4 years ago
|
||
Updated•4 years ago
|
Comment 31•4 years ago
|
||
| Assignee | ||
Comment 32•4 years ago
|
||
Updated•4 years ago
|
Updated•4 years ago
|
Updated•4 years ago
|
Comment 33•4 years ago
|
||
Landed: https://hg.mozilla.org/integration/autoland/rev/d2ec4915e72537eea1be997be7979a97ab32c268
Backout link : https://hg.mozilla.org/integration/autoland/rev/af41fc60c4ca961c42870054fd6f807fb3ec9171
Push with failures :
https://treeherder.mozilla.org/jobs?repo=autoland&resultStatus=pending%2Crunning%2Csuccess%2Ctestfailed%2Cbusted%2Cexception%2Crunnable&searchStr=android%2C7.0%2Cx86-64%2Cwebrender%2Cdebug%2Cxpcshell%2Ctests%2Ctest-android-em-7.0-x86_64-qr%2Fdebug-geckoview-xpcshell-e10s%2Cx1&revision=63a236bcd71343377bc62f5cf58ea4491e3b97d2
https://treeherder.mozilla.org/jobs?repo=autoland&resultStatus=testfailed%2Cbusted%2Cexception%2Crunnable&revision=d2ec4915e72537eea1be997be7979a97ab32c268&selectedTaskRun=JYm2NC9GQr6KXUNHEjA0KA.0
Link to failure logs:
https://treeherder.mozilla.org/logviewer?job_id=370983321&repo=autoland&lineNumber=4465
https://treeherder.mozilla.org/logviewer?job_id=370984043&repo=autoland&lineNumber=2389
https://treeherder.mozilla.org/logviewer?job_id=370984310&repo=autoland&lineNumber=1655
https://treeherder.mozilla.org/logviewer?job_id=370985673&repo=autoland&lineNumber=2634
| Assignee | ||
Comment 34•4 years ago
|
||
I'm surprised we missed those in the latest try push from yesterday. Maybe "mach try auto" was not enough? Anyway, I'll fold them into my patch and try again, when I get back. Thanks!
Comment 35•4 years ago
|
||
Gijs, could you update your patch to fix test_import_fail.js, too? I think the breakage is fallout from that. Then we could land this, even before Freddy is back. Thanks.
| Assignee | ||
Comment 36•4 years ago
|
||
I had a diff that should work, but doesn't and I'm indeed unlikely to finish to this before I get back Apr 4th.
diff --git a/toolkit/components/extensions/moz.build b/toolkit/components/extensions/moz.build
index e2f408da316a9..8cc1fc1bec37e 100755
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -53,6 +53,8 @@ TESTING_JS_MODULES += [
"ExtensionTestCommon.jsm",
"ExtensionXPCShellUtils.jsm",
"MessageChannel.jsm",
+ "test/xpcshell/data/TestWorkerWatcherChild.jsm",
+ "test/xpcshell/data/TestWorkerWatcherParent.jsm",
]
DIRS += [
diff --git a/toolkit/components/extensions/test/xpcshell/head_service_worker.js b/toolkit/components/extensions/test/xpcshell/head_service_worker.js
index a2c8cf326039a..cefd26f6afda2 100644
--- a/toolkit/components/extensions/test/xpcshell/head_service_worker.js
+++ b/toolkit/components/extensions/test/xpcshell/head_service_worker.js
@@ -111,15 +111,12 @@ class TestWorkerWatcher extends ExtensionCommon.EventEmitter {
registerProcessActor() {
const { JS_ACTOR_NAME } = this;
- const getModuleURI = fileName =>
- Services.io.newFileURI(do_get_file(`${this.dataRelPath}/${fileName}`))
- .spec;
ChromeUtils.registerProcessActor(JS_ACTOR_NAME, {
parent: {
- moduleURI: getModuleURI(`${JS_ACTOR_NAME}Parent.jsm`),
+ moduleURI: `resource://testing-common/${JS_ACTOR_NAME}Parent.jsm`,
},
child: {
- moduleURI: getModuleURI(`${JS_ACTOR_NAME}Child.jsm`),
+ moduleURI: `resource://testing-common/${JS_ACTOR_NAME}Child.jsm`,
},
});
}
I tried debugging with this diff, but didn't get very far:
diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp
index 104f260f1779b..19639074ee853 100644
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -1256,6 +1256,9 @@ nsresult mozJSComponentLoader::Import(JSContext* aCx,
// Reject imports from untrusted sources.
if ((!info.URI()->SchemeIs("resource")) &&
(!info.URI()->SchemeIs("chrome"))) {
+ nsAutoCString scheme;
+ info.URI()->GetAsciiSpec(scheme);
+ fprintf(stderr, "\n\nTEST-UNEXPECTED-FAIL import w/ scheme %s\n\n", scheme.get());
return NS_ERROR_DOM_SECURITY_ERR;
}
Comment 37•4 years ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #35)
Gijs, could you update your patch to fix test_import_fail.js, too? I think the breakage is fallout from that. Then we could land this, even before Freddy is back. Thanks.
Sorry, I've been kind of swamped. I think there are at least 3 other tests that also failed per comment #33, but I'll try taking a look.
Comment 38•4 years ago
•
|
||
test_import_fail.js failure is android-only - it passed on Linux on the trypush and it passes for me on macOS locally. It's complaining that the exception object it gets doesn't have a fileName property. I'll push to try to figure out what's wrong there, I guess, but I'll look at the other ones first.
Comment 39•4 years ago
|
||
Oh, OK, it's because my other patch removes that file from the list of support-files, and the code in test_import_fail.js loads the same file using resource://test/ which fails when we don't copy the file using the support-files mechanism. I'm not reproducing locally because I've already done a build and without a clobber, the objdir still has those support files in it so the test is happy.
This actually shows that we should just be using resource://test/ to access these files. I'll change all the test updates to use that (much smaller changeset), fold it into the single patch from Freddy and push to try.
Comment 40•4 years ago
|
||
Updated•4 years ago
|
Updated•4 years ago
|
Comment 41•4 years ago
|
||
Backed out for causing bug 1762611.
Backout link: https://hg.mozilla.org/integration/autoland/rev/14989613a60566a4f7cd4615daff1fe9a0dae9d4
| Assignee | ||
Comment 42•4 years ago
|
||
Looks like this can land once jdescottes has fixed bug 1762611.
Comment 43•4 years ago
|
||
mozJSComponentLoader::Import() should only work for internal URLs, and update some tests r=mccr8
https://hg.mozilla.org/integration/autoland/rev/fafb7b2268f385506c5c650c819ac89f8a5b1648
https://hg.mozilla.org/mozilla-central/rev/fafb7b2268f3
Comment 44•4 years ago
|
||
Is this something we were thinking we wanted in 100 ahead of pwn2own?
| Assignee | ||
Comment 45•4 years ago
|
||
I don't believe this will be make a meaningful difference given the preconditions required to carry out an attack against ChromeUtils.import().
Tom, objections?
Updated•4 years ago
|
Updated•4 years ago
|
Updated•4 years ago
|
Updated•3 years ago
|
Description
•