Bug 15003

Summary: Function.prototype.constructor should not be DontDelete/ReadOnly (Acid3 bug)
Product: WebKit Reporter: Jeff Walden (remove +bwo to email) <jwalden+bwo>
Component: JavaScriptCoreAssignee: Darin Adler <darin>
Status: RESOLVED FIXED    
Severity: Normal CC: darin, ddkilzer, eric, ggaren, ian, mjs, sam
Priority: P2    
Version: 523.x (Safari 3)   
Hardware: All   
OS: All   
URL: javascript:function yn(v) { return v ? "yes" : "no"; }; function c() { }; delete c.prototype.constructor; var i = new c(); var res = (i.constructor !== Object.prototype.constructor); var allres = !res; var msg = "DontDelete: " + yn(res) + "\n"; function g() { }; g.prototype.constructor = 17; var i2 = new g(); res = (i2.constructor !== 17); allres = allres && !res; msg += "ReadOnly: " + yn(res) + "\n"; function h() { }; res = true; for (var prop in new h()) { if (prop == "constructor") res = false; }; msg += "DontEnum: " + yn(res); allres = allres && res; alert("Function.prototype.constructor attribute checks " + (allres ? "PASS" : "FAIL") + "\n" + msg);
Bug Depends on:    
Bug Blocks: 17064    
Attachments:
Description Flags
patch eric: review+

Description Jeff Walden (remove +bwo to email) 2007-08-19 02:14:41 PDT
13.2 Creating Function Objects

10. Set the "constructor" property of Result(9) [the newly-created prototype object for the Function object being created] to F [the Function object being created].  This property is given attributes { DontEnum }.


This breaks hiding the constructor of an object from any code which sees that object, as an internals-hiding mechanism for constructor privateness.


The relevant place to fix, at a glance, is:

http://trac.webkit.org/projects/webkit/browser/trunk/JavaScriptCore/kjs/function_object.cpp#L248

It should be trivial to convert the URL test into a JavaScriptCore test and to remove the extra attributes from that line -- for someone who has the time to do that, that is.
Comment 1 David Kilzer (:ddkilzer) 2007-08-19 08:53:04 PDT
I believe there may be similar changes required in JavaScriptCore/kjs/nodes.cpp for FuncDeclNode::processFuncDecl(ExecState*) and FuncExprNode::evaluate(ExecState*) if this does indeed fix the bug.  (I think the duplicate code should be refactored into its own method as well!)

This bug reminded me of revision r19203:
http://trac.webkit.org/projects/webkit/changeset/19203

Test case confirmed with a local debug build of WebKit r25142 with Safari 3 Public Beta v. 3.0.3 (522.12.1) on Mac OS X 10.4.10 (8R218).

Both Opera 9.22 and Firefox 2.0.0.6 pass the given test.

Comment 2 Ian 'Hixie' Hickson 2007-12-29 18:51:50 PST
In the DontDelete test: I don't understand why the instance's constructor property would become Object.prototype.constructor instead of being removed altogether.
Comment 3 Jeff Walden (remove +bwo to email) 2007-12-29 20:52:46 PST
(In reply to comment #2)
> In the DontDelete test: I don't understand why the instance's constructor
> property would become Object.prototype.constructor instead of being removed
> altogether.

It *is* removed, but you're only removing it from c.prototype, which per 13.2.2 step 4 and 13.2 steps 9-11 is a value created as if by new Object().  That object has as its prototype the Object prototype object (15.2.4), which has a constructor property which is Object.prototype.constructor, i.e. the built-in Object constructor (15.2.4.1), so getting the constructor property of |new c()| won't find it on that object, won't find it on the auto-created prototype, but will find it on Object.prototype.
Comment 4 Eric Seidel (no email) 2007-12-29 21:12:25 PST
Acid3, test 87 tests for this bug.  I would think it would be as simple as changing:
JSObject* FunctionObjectImp::construct(ExecState* exec, const List& args, const Identifier& functionName, const UString& sourceURL, int lineNumber)

to not put() the constructor property with ReadOnly an DontDelete.
Comment 5 Sam Weinig 2007-12-29 21:19:29 PST
As Mr. Kilzer has already pointed out, you would also need to change FuncDeclNode::makeFunction.
Comment 6 Ian 'Hixie' Hickson 2008-01-06 22:14:45 PST
(In reply to comment #3)

Thanks, that makes sense.
Comment 7 Darin Adler 2008-02-08 03:52:56 PST
Created attachment 19003 [details]
patch
Comment 8 Eric Seidel (no email) 2008-02-08 14:09:48 PST
Comment on attachment 19003 [details]
patch

the code change looks fine.  I have concerns about the testing coverage.  It should be possible to create a test which walked all functions on the window object and checked to make sure that their prototype.constructor was mutable, no?  What about all of the HTML* elements which are being changed by your bindings .pl change.  I would think those could be tested in some automated way.  Perhaps the ideal for this sort of thing is to find a way for fast/window-properties.html  to dump all of the DontDelete|DontEnum|ReadOnly information.
Comment 9 Darin Adler 2008-02-08 15:09:32 PST
(In reply to comment #8)
> It should be possible to create a test which walked all functions on the window
> object and checked to make sure that their prototype.constructor was mutable,
> no?  What about all of the HTML* elements which are being changed by your
> bindings .pl change.  I would think those could be tested in some automated
> way.

The reason I included the Node test was that I wanted to test at least one auto-generated example. I agree that we could expand the test to make sure it tests all the other generated constructors too.
Comment 10 Jeff Walden (remove +bwo to email) 2008-02-08 15:24:37 PST
For the record, since I recently heard skepticism expressed on IRC over the value of this:

Sometime near the beginning of August, Ajaxian had a blurb on Neil Mix's examination of Facebook's application platform and its security.  For those who don't know, Facebook's app system consists of writing apps in a subset of HTML and JavaScript, modulo some Facebook-specific additions.  The usual suspects are removed from HTML to make it safe, and the JS is run through a source-to-source translator to prevent unguarded access to any global variables, properties, etc.  Runtime checks are inserted in various places to make checks which can't be done statically.  This final code is then run on the facebook.com domain, with the full privileges of any facebook.com JS.  The rewriting and checking are critical to preventing arbitrary XSS.

I read Neil's posts on the exploits he found, and I decided to do some investigation of my own.  Over the next couple weeks I found a good handful of exploits beyond the ones Neil found, as did a coworker at the time.  Many of the exploits relied on Mozilla-specific features because that's what I happen to know best, but a fair number of them would work in any browser.

By now you might be able to see where this is going.  Neil Mix's original exploit was to do the following:

 (function(){}).constructor("alert('hi')")();

He reported this on his blog and noted the Facebook fix:

 Function.prototype.constructor = null;

This of course failed silently and horribly in Safari -- but nobody noticed.  Only later during the exploit-finding, when we realized that their strategy for preventing access to Facebook-internal methods (fbjs_event being one, if I remember correctly) was in each constructor to assign |this.constructor = null;|, was it discovered this didn't work in Safari.  In the end the solution was to ban accessing the constructor property of any object, ever -- through translation-time changes when possible, and through runtime checks of property accesses when not.  (This was already necessary for __defineGetter__, __defineSetter__, __proto__, caller, and several other properties, so it wasn't onerous, but it *was* unnecessary.)

Either having constructor be deletable or mutable -- either one -- would have fixed this problem and allowed the constructor property to be safely exposed (assuming proper vigilance by Facebook in removing the constructor from their own functions, when needed).  Real-world code relied (probably not based on a spec reading, but whatever) on changing/removing the constructor property from JS objects for security, and it was broken by this bug.

This bug report was the result of email conversation with Facebook's security people after they discovered the problem; I didn't think it a great idea to be explicit about what the problem was as they hadn't fixed the problem at the time, so I hand-waved a bit and claimed this was only important "as an internals-hiding mechanism", but that wasn't really the whole story.
Comment 11 Darin Adler 2008-02-08 15:28:16 PST
(In reply to comment #10)
> For the record, since I recently heard skepticism expressed on IRC over the
> value of this:

I think you probably misunderstood what you overheard.

Maciej and I were joking about our responsibilities at Apple. It wasn't about the importance of this particular bug at all.
Comment 12 Darin Adler 2008-02-08 15:30:02 PST
(In reply to comment #9)
> The reason I included the Node test was that I wanted to test at least one
> auto-generated example. I agree that we could expand the test to make sure it
> tests all the other generated constructors too.

By "the Node test", I mean the test case that uses a Text node.
Comment 13 Darin Adler 2008-02-08 17:06:43 PST
Committed revision 30102.