Closed Bug 853709 (CVE-2013-1670) Opened 11 years ago Closed 11 years ago

Its possible to call a content level constructor as if from a chrome/privileged page

Categories

(Core :: XPConnect, defect)

19 Branch
x86_64
Windows 7
defect
Not set
normal

Tracking

()

VERIFIED FIXED
mozilla22
Tracking Status
firefox19 --- wontfix
firefox20 --- wontfix
firefox21 + verified
firefox22 + verified
firefox35 --- fixed
firefox-esr17 21+ verified
b2g18 21+ fixed
b2g18-v1.0.0 --- wontfix
b2g18-v1.0.1 --- affected

People

(Reporter: codycrews00, Assigned: bholley)

References

Details

(Keywords: sec-high, Whiteboard: [adv-main21+][adv-esr1706+][adv-main35-])

Attachments

(3 files)

Attached file ccpoc.html
User Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0
Build ID: 20130307023931

Steps to reproduce:

I used the mozContact constructor to gain access to a chrome level array and then added a content level constructor to it and defined a setter to do the calling for me.


Actual results:

The content level constructor behaves as if called from a privileged page as shown from the error, this works with most content level constructors(that are function objects) but using the File constructor was just the easiest way to show this off.  I had discussed this issue in passing in an email with Bobby Holly but hadn't had an easy way to show it off.


Expected results:

Chrome level Objects exposed to content should be careful about any properties that are 'rw' as defined in __exposedProps__, I think it may be best to avoid it at all if possible.  Arrays from chrome and the normal COWs(this works with them too) are going to have to be rethought.  This happens I believe because Function Objects inherit their principals from the nearest Object in the scope of their execution.
Attachment #728007 - Attachment mime type: text/plain → text/html
Component: Untriaged → XPConnect
Product: Firefox → Core
Testcase works in a Desktop Nightly. Nice job, Cody.
Status: UNCONFIRMED → NEW
Ever confirmed: true
Flags: sec-bounty?
Keywords: sec-high
Looks this there's a couple of things going on here. I'll look at it again when I'm less tired.
Yeah there's definitely a couple of things to this.  Without the ability to define setters/getters on COW's or arrays the Function objects will continue to act as if called from content.  The 'this' keyword always becomes the object of and I believe the beginning of the lexical scope when dealing with setters and getters even when inherited through the prototype chain.  Also obtaining a reference to the created object would pose another problem and yet could probably be done, but at present just with the File constructor and using error messages it would be possible to map a users local Filesystem giving information about dlls and other files which could be used to bypass ASLR etc.  I usually like to fully work out what all can be done with something before filing but that alone seemed serious enough for this bug to be filed ASAP.
Cody, this is extremely clever.

For those who don't want to pore over the testcase, here's what it does:

* It creates a mozContact, which is implemented as a COW-wrapped chrome object, with an array marked 'rw'.
* It puts the File constructor at index 0 in the array.
* It then defines a setter at index 1 whose setter function is index 0 of the array, i.e. the File constructor.
* It does arr[1] = 'c:\some\file\path\'. This trips the setter, which ends up invoking the File constructor with the associated string as its only argument.

The key point here is that the File constructor never gets any sort of Xray waiver around it, and so the chrome caller invokes it with Xray vision, thus never entering the compartment and never dropping from chrome principal to page principal. So the IsCallerChrome() check in nsDOMMultipartFile::InitFile succeeds. Yikes.

Now, the real question is how to fix this in the general case. Our security model assumes that it's safe to call an Xrayed function because it never enters the content compartment, meaning that there's no opportunity for the target to confuse it. But here we have the content entering the chrome compartment instead, and setting things up so that the chrome code shoots itself in the foot. :-(

There are number of mitigation techniques we can apply here:
* Stop defining (or even supporting?) rw properties on COWs.
* Don't allow accessors to be defined on COWs.

But in general, I think we fundamentally need to move away from letting content muck with privileged objects at all.
Assignee: nobody → bobbyholley+bmo
Flags: sec-bounty? → sec-bounty+
This one was one that I was particularly interested in, yet there's still a few more things that I feel could lead to issues, though nothing quite like this.  As to ways to mitigate this I'm sort of at a loss. I mean Bobby Holly has presented the viable options and its just about how much code relies on COW's with 'rw' properties.  I'm pretty tired tonight but tomorrow I'll try to dig through mozilla internal code that those fixes might affect, addons will be a different story though.
So the fundamental issue here is that chrome is ending up with an Xray for a function when it doesn't expect one, right?

Disallowing accessors on COWs seems like a good step.  I doubt anyone setting a property 'w' expects that to mean "content can redefine it as an accessor property".

But past that, I agree; we need to move away from letting content mess directly with privileged objects.  The question is how we get there...
(In reply to Boris Zbarsky (:bz) from comment #7)
> Disallowing accessors on COWs seems like a good step.  I doubt anyone
> setting a property 'w' expects that to mean "content can redefine it as an
> accessor property".

For some reason, treating accessor properties specially rubs me the wrong way. I did it for some XPCNativeWrapper holes back in the day, but it never felt right. I guess my question is: what is the fundamental difference between accessor properties and calling functions?

> But past that, I agree; we need to move away from letting content mess
> directly with privileged objects.  The question is how we get there...

What do you mean with "mess with?" As long as we want to implement privileged APIs in chrome JS, we're going to have interaction between content and chrome (at the very least in the form of function parameters).

Can we make functions passed to chrome be non-waiver non-Xray wrappers (so the only Xray functions called by chrome would be functions explicitly gotten off of existing Xray wrapper objects which have guaranteed behavior)?
> what is the fundamental difference between accessor properties and calling functions?

The fact that the latter is syntactically obvious to the chrome code, and thus easier to avoid, imo.

> What do you mean with "mess with?" 

"Put them into a state the chrome code operating on those objects does not expect them to be in".

> As long as we want to implement privileged APIs in chrome JS, we're going to have
> interaction between content and chrome (at the very least in the form of function
> parameters).

To some extent.  For example, in the JS-implemented WebIDL most function parameters are ensured to be sane (in the sense of not having any unexpected behavior) by the binding layer, with the obvious exceptions being explicit "object" and "any" arguments and callbacks.

> Can we make functions passed to chrome be non-waiver non-Xray wrappers 

So what I proposed to Bobby earlier today is that _calling_ a DOM interface object Xray should simply enter the content compartment for the call, just like an Xray for a scripted function would.  These are Xrays because we want to be able to get at their properties, not because we want to call them in the chrome compartment, imo.
(In reply to Blake Kaplan (:mrbkap) from comment #8)
> Can we make functions passed to chrome be non-waiver non-Xray wrappers (so
> the only Xray functions called by chrome would be functions explicitly
> gotten off of existing Xray wrapper objects which have guaranteed behavior)?

This is effectively a third state for Xrayable objects, which I'm not really wild about implementing. I'd much prefer to just remove the ability for content to inject functions into the chrome scope.

(In reply to Boris Zbarsky (:bz) from comment #9)

> > As long as we want to implement privileged APIs in chrome JS, we're going to have
> > interaction between content and chrome (at the very least in the form of function
> > parameters).
> 
> To some extent.  For example, in the JS-implemented WebIDL most function
> parameters are ensured to be sane (in the sense of not having any unexpected
> behavior) by the binding layer, with the obvious exceptions being explicit
> "object" and "any" arguments and callbacks.

I agree with bz here that it's significantly harder to mount an attack like this with a C++ layer in between. And I think we can get there. The stuff in mozilla-central should just be moved to WebIDL, and the jetpack folks are moving towards an API that does everything via makeObjectPropsNormal-style function wrappers.

> So what I proposed to Bobby earlier today is that _calling_ a DOM interface
> object Xray should simply enter the content compartment for the call

As mentioned, I disagree here. I think it significantly muddles the Xray semantics.

> just like an Xray for a scripted function would. 

There isn't really such a thing. It's true that we currently default to transparent for non-waived non-Xrayable objects, but I consider that to be a legacy bug that I'd like to move away from, if possible. IMO any raw JS objects used by chrome should be wrapped in a waiver.
This is a hard problem and has been causing headaches for a while, importing pure JS objects from chrome to content. While I always thought about providing the least limiting high level API to JS developers is the best, I kind of gave up on it.

Based on the talks I had in the past few weeks with pure JS developers, my impression is that they tend to prefer a limiting low level API a lot more, than dealing with weird, non standard JavaScript wrappers with __exposedProps__ magics, or that twin object version we came up with the last DOM work week. The trick we use in makeObjectPropsNormal seems to be quite safe way for exporting functions (only) to content. And if we only expose that trick to JS developers they seems to be happy to do everything else in JS (for states they can simply use closures, for simple object they can just use structure clone, etc.). This whole approach seems to be very safe (there is only a very thin layer between chrome and content to guard), and very flexible (the high level utility functions for exposing various more complex objects in some way is implemented in standard JS unlike __exposedProps__ or makeObjectPropsNormal), plus it is a lot easier to explain to JS people who are the main users of this API. That's just my two cents.
Flags: in-testsuite?
Attached patch Tests. v1Splinter Review
Attachment #731235 - Flags: review?(mrbkap)
Comment on attachment 731234 [details] [diff] [review]
Deny accessor definitions in SecurityWrapper. v1

Pre-emptively flagging for sec-approval in case we decide we want to take this before the merge.

[Security approval request comment]
How easily could an exploit be constructed based on the patch?

Somewhat. Cody's exploit uses a number of tricks, and this fixes one of them. In general, defining a getter or setter on a COW wouldn't seem to be a huge problem, because we'd always enter the content compartment. So they'd have to think of the Xray trick here.

Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?

It's pretty clear that we don't want accessors to be defined on security wrappers. But I managed to make this patch general to all security wrappers and avoid talking about COWs at all.

Which older supported branches are affected by this flaw?

All.

Do you have backports for the affected branches? If not, how different, hard to create, and risky will they be?

This patch should work everywhere we have bug 800915, which should be all supported branches at this point.

How likely is this patch to cause regressions; how much testing does it need?

It should probably be fine. The only regression would be if some COW (maybe in an extension, though it's unlikely) was depending on having content define accessor properties. We now explicitly throw a very descriptive error message in that case (that's the whole point of the patch). That's probably unlikely.
Attachment #731234 - Flags: sec-approval?
Attachment #731234 - Flags: sec-approval? → sec-approval+
Attachment #731234 - Flags: review?(mrbkap) → review+
Comment on attachment 731234 [details] [diff] [review]
Deny accessor definitions in SecurityWrapper. v1

[Approval Request Comment]
Bug caused by (feature/regressing bug #): longstanding issue, exploitable via the contact API
User impact if declined: security bugs 
Testing completed (on m-c, etc.): just pushed to m-i
Risk to taking this patch (and alternatives if risky): Not very risky. No real alternatives.
String or IDL/UUID changes made by this patch: There's a change to js.msg, but I don't believe that affects l10n (correct me if I'm wrong).
Attachment #731234 - Flags: approval-mozilla-esr17?
Attachment #731234 - Flags: approval-mozilla-beta?
Attachment #731234 - Flags: approval-mozilla-b2g18?
Comment on attachment 731234 [details] [diff] [review]
Deny accessor definitions in SecurityWrapper. v1

I guess I meant aurora here, which will be beta in a few days. :-)
Attachment #731234 - Flags: approval-mozilla-beta? → approval-mozilla-aurora?
https://hg.mozilla.org/mozilla-central/rev/8ea262c8bb9c
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla22
Comment on attachment 731235 [details] [diff] [review]
Tests. v1

Review of attachment 731235 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/xpconnect/tests/unit/test_bug853709.js
@@ +30,5 @@
> +
> +  checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }});
> +  checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }});
> +  checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}});
> +  checkDefineThrows(contentSB, 'chromeArr', '1', {configurable: true, get: function() { return 2; }});

Also add a test for setter-only properties?
Attachment #731235 - Flags: review?(mrbkap) → review+
Attachment #731234 - Flags: approval-mozilla-esr17?
Attachment #731234 - Flags: approval-mozilla-esr17+
Attachment #731234 - Flags: approval-mozilla-beta+
Attachment #731234 - Flags: approval-mozilla-b2g18?
Attachment #731234 - Flags: approval-mozilla-b2g18+
Attachment #731234 - Flags: approval-mozilla-aurora?
Looks like beta is going to need some love too. Backed out for OSX and Windows build bustage.
https://hg.mozilla.org/releases/mozilla-beta/rev/8ce4ff5714bd

https://tbpl.mozilla.org/php/getParsedLog.php?id=21399661&tree=Mozilla-Beta

In file included from ../../../../js/src/jswrapper.cpp:15:
../../../../js/src/jswrapper.h:190:18: error: 'defineProperty' marked 'override' but does not override any member functions
    virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                 ^
../../../../js/src/jswrapper.cpp:892:20: note: in instantiation of template class 'js::SecurityWrapper<js::Wrapper>' requested here
template class js::SecurityWrapper<Wrapper>;
                   ^
In file included from ../../../../js/src/jswrapper.cpp:15:
../../../../js/src/jswrapper.h:190:18: warning: 'js::SecurityWrapper<js::Wrapper>::defineProperty' hides overloaded virtual function [-Woverloaded-virtual]
    virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                 ^
../../../../js/src/jswrapper.h:90:18: note: hidden overloaded virtual function 'js::Wrapper::defineProperty' declared here
    virtual bool defineProperty(JSContext *cx, JSObject *wrapper, jsid id,
                 ^
../../../../js/src/jswrapper.h:190:18: error: 'defineProperty' marked 'override' but does not override any member functions
    virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                 ^
../../../../js/src/jswrapper.cpp:893:20: note: in instantiation of template class 'js::SecurityWrapper<js::CrossCompartmentWrapper>' requested here
template class js::SecurityWrapper<CrossCompartmentWrapper>;
                   ^
In file included from ../../../../js/src/jswrapper.cpp:15:
../../../../js/src/jswrapper.h:190:18: warning: 'js::SecurityWrapper<js::CrossCompartmentWrapper>::defineProperty' hides overloaded virtual function [-Woverloaded-virtual]
    virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                 ^
../../../../js/src/jswrapper.h:138:18: note: hidden overloaded virtual function 'js::CrossCompartmentWrapper::defineProperty' declared here
    virtual bool defineProperty(JSContext *cx, JSObject *wrapper, jsid id,
                 ^
2 warnings and 2 errors generated.
make[6]: *** [jswrapper.o] Error 1
You just need to change the HandleObject to JSObject* and the HandleId to jsid (in both the header and the cpp file).

https://hg.mozilla.org/releases/mozilla-beta/rev/4ad0a5f5f017
Alias: CVE-2013-1670
Whiteboard: [adv-main21+][adv-esr1706+]
Reproduced on FF17.0.5esr
Confirmed fixed on FF17.0.6esr candidate build 1
Confirmed fixed on FF21 candidate build 2
Confirmed fixed on FF22 2013-05-09
Confirmed fixed on FF23 2013-05-09
Flags: in-testsuite? → in-testsuite+
Blocks: 1055189
Group: core-security
Whiteboard: [adv-main21+][adv-esr1706+] → [adv-main21+][adv-esr1706+][adv-main35-]
You need to log in before you can comment on or make changes to this bug.