Closed Bug 1101123 Opened 5 years ago Closed 5 years ago

Make it impossible to use JS_Define* to redefine a non-configurable getter on a native object

Categories

(Core :: JavaScript Engine, defect)

defect
Not set

Tracking

()

RESOLVED FIXED
mozilla37

People

(Reporter: bzbarsky, Assigned: bzbarsky)

References

Details

Attachments

(1 file, 2 obsolete files)

Then we can do the optimization in bug 1100757.
I'm going to scope this down a bit, because the full generality here is not actually viable.
Summary: Make it impossible to use JS_Define* to redefine non-configurable props → Make it impossible to use JS_Define* to redefine a non-configurable getter on a native object
Assignee: nobody → bzbarsky
Status: NEW → ASSIGNED
So apparently sandbox also violates this invariant.  Specifically, in test_writeToGlobalPrototype.js we have code like this:

50   Cu.evalInSandbox("Object.defineProperty(this, 'y', {get: function() { this.gotten = true; return 3; }})", s);

with this patch, this throws, because sandbox_addProperty, which is called _after_ the prop has been added, does something to the proto, and then does:

445     if (!JS_GetPropertyDescriptorById(cx, obj, id, &pd))
446         return false;
447     unsigned attrs = pd.attributes() & ~(JSPROP_GETTER | JSPROP_SETTER);
448     if (!JS_DefinePropertyById(cx, obj, id, vp,
449                                attrs | JSPROP_PROPOP_ACCESSORS,
450                                JS_PROPERTYOP_GETTER(writeToProto_getProperty),
451                                JS_PROPERTYOP_SETTER(writeToProto_setProperty)))
452         return false;

which is totally redefining the accessor for a non-configurable property.  Bill, what's this code trying to do and can we make it stop violating basic invariants?
Flags: needinfo?(wmccloskey)
And similar for Xrays.  In test_xrayToJS.xul we have:

    trickyObject.getterSetterProp = 'redefined';

(though for some reason the actual line number SpiderMonkey reports is off-by-one).

In this case "trickyObject" is actually trickyArray, which had this done to it:

375                  Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}});

Anyway, we land in js::Proxy::set which gets a property descriptor, finds nothing (because it's an Xray that filters out the prop), and does JSObject::defineGeneric to define a value property.  That lands us in xpc::JSXrayTraits::defineProperty which then tried to redefine the property on the target.  But the property on the target is non-configurable, so this throws.

Bobby, what are the intended semantics of Xrays here, exactly? Because what they're doing right now (reconfiguring content-side non-configurable properties) is not so great...
Flags: needinfo?(bobbyholley)
(In reply to Please do not ask for reviews for a bit [:bz] from comment #4)
> Bobby, what are the intended semantics of Xrays here, exactly? Because what
> they're doing right now (reconfiguring content-side non-configurable
> properties) is not so great...

I think we discussed this in the DOM bindings meeting sometime in June, right? I think we concluded that there are no easy solutions here and that we should just propagate the non-configurable exception to the Xray caller (which I _thought_ was what we were doing, but apparently not).

The other option would be to use the expando object in this case but that's probably more trouble and confusion than it's worth.
Flags: needinfo?(bobbyholley)
Attachment #8525737 - Attachment is obsolete: true
Attachment #8525737 - Flags: review?(efaustbmo)
Comment on attachment 8526204 [details] [diff] [review]
With the xray test adjusted to the behavior we want and now have

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

r=me on the test fix. Thanks for doing that.
Attachment #8526204 - Flags: review?(bobbyholley) → review+
The sandbox_addProperty code is trying to intercept new properties added to certain globals and modify them to have a special getter and setter. I can imagine a few ways to deal with this:

1. Add a new hook that's similar to addProperty but runs before the property is added. I'm guessing there will be a lot of resistance to adding a new hook though.

2. It sounds like this problem doesn't necessarily negate the optimization in bug 1100757 since (I think) the property is re-defined before the JIT ever sees it. However, I'm guessing that you want to make JS_Define* fail when re-defining a non-configurable property, so we'd have to hack around that somehow in the sandbox_addProperty case. It would probably be a little gross.

3. We could use a proxy instead of a class hook. However, we don't support proxies as globals, so we'd have to implement support for that. I suspect it would be a ton of work.
Flags: needinfo?(wmccloskey)
> since (I think) the property is re-defined before the JIT ever sees it.

In this case, yes.  But yes, I want to make this not allowed at all, because otherwise it's too big a footgun.

For now, can we not just switch from JS_NULL_OBJECT_OPS to ops that have define* hooks that do what we want for SandboxWriteToProtoClass?  We'd probably need to expose baseops::Define* via some sort of friend API so we can forward to it, but other than that...
Flags: needinfo?(jwalden+bmo)
(In reply to Please do not ask for reviews for a bit [:bz] from comment #9)
> For now, can we not just switch from JS_NULL_OBJECT_OPS to ops that have
> define* hooks that do what we want for SandboxWriteToProtoClass?

This seems about right, to have the sandbox act as a proxy that intercepts behaviors it wants to wrap.

> We'd
> probably need to expose baseops::Define* via some sort of friend API so we
> can forward to it, but other than that...

I think this can be avoided with a delegate object of sorts.  I'd really like to avoid exposing baseops stuff if at all possible; it's too low-level to be a sensible API, it's hard to describe, hard to know when to use, and just not a good API even for the friendzone incest.
Flags: needinfo?(jwalden+bmo)
So I was thinking we wouldn't need the extra object, but we do, because sandbox wants to forward some, but not all, definitions to the proto global..
So... I'm not sure how to create sane define/lookup hooks without access to JSObject::lookupGeneric/defineGeneric.  Define I might be able to do via jsapi, but I see nothing reasonable for lookup...  Jeff, do we want to just add friend API for that?
Flags: needinfo?(jwalden+bmo)
Temporary friendapi seems reasonable, if it's needed.  It looks like JS_Lookup{Property,Element} would do what you want, but you presumably have only jsid, and JS_LookupPropertyById is not (!) the same semantics as those two.  So you probably need something new.

I thought this might overlap with jorendorff work on adjusting uses of lookups to not be so stupid sometimes, but IRC suggests not, so no worries on that front, seemingly.
Flags: needinfo?(jwalden+bmo)
Attachment #8526204 - Attachment is obsolete: true
Attachment #8526204 - Flags: review?(efaustbmo)
Comment on attachment 8529314 [details] [diff] [review]
With a hackaround for the sandbox bits

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

Man, this is ugly, but it's also what we agreed upon. Sigh. I hope this is removed quickly.
Attachment #8529314 - Flags: review?(efaustbmo) → review+
https://hg.mozilla.org/mozilla-central/rev/c44465f2a483
Status: ASSIGNED → RESOLVED
Closed: 5 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla37
You need to log in before you can comment on or make changes to this bug.