Fixes CVE-2019-11750. Backported from mozjs-68esr: https://hg.mozilla.org/releases/mozilla-esr68/rev/b1482a07eafe84c33351cb4524404686a9fce427 # HG changeset patch # User Jan de Mooij # Date 1565773483 0 # Node ID b1482a07eafe84c33351cb4524404686a9fce427 # Parent e432c2b3ca00d0f6fd14168161f47b929db8c901 Bug 1568397 part 1 - Fix definite properties analysis to use the correct group for constraints. r=iain,tcampbell, a=RyanVM We now add information about the constraints to a new class (DPAConstraintInfo) so we can then finish all constraints at the end. This is also nice to avoid adding unnecessary constraints when the analysis fails. Differential Revision: https://phabricator.services.mozilla.com/D40444 --- mozjs-60.8.0/js/src/jit/IonAnalysis.cpp +++ mozjs-60.8.0/js/src/jit/IonAnalysis.cpp @@ -3722,9 +3722,9 @@ MCompare* jit::ConvertLinearInequality(T } static bool AnalyzePoppedThis( - JSContext* cx, ObjectGroup* group, MDefinition* thisValue, - MInstruction* ins, bool definitelyExecuted, HandlePlainObject baseobj, - Vector* initializerList, + JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, + MDefinition* thisValue, MInstruction* ins, bool definitelyExecuted, + HandlePlainObject baseobj, Vector* initializerList, Vector* accessedProperties, bool* phandled) { // Determine the effect that a use of the |this| value when calling |new| // on a script has on the properties definitely held by the new object. @@ -3756,7 +3756,12 @@ static bool AnalyzePoppedThis( if (!definitelyExecuted) return true; RootedId id(cx, NameToId(setprop->name())); - if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, group, id)) { + bool added = false; + if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, + group, id, &added)) { + return false; + } + if (!added) { // The prototype chain already contains a getter/setter for this // property, or type information is too imprecise. return true; @@ -3815,7 +3820,12 @@ static bool AnalyzePoppedThis( if (!baseobj->lookup(cx, id) && !accessedProperties->append(get->name())) return false; - if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, group, id)) { + bool added = false; + if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, + group, id, &added)) { + return false; + } + if (!added) { // The |this| value can escape if any property reads it does go // through a getter. return true; @@ -3839,8 +3849,8 @@ static int CmpInstructions(const void* a } bool jit::AnalyzeNewScriptDefiniteProperties( - JSContext* cx, HandleFunction fun, ObjectGroup* group, - HandlePlainObject baseobj, + JSContext* cx, DPAConstraintInfo& constraintInfo, + HandleFunction fun, ObjectGroup* group, HandlePlainObject baseobj, Vector* initializerList) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); @@ -3991,9 +4001,9 @@ bool jit::AnalyzeNewScriptDefiniteProper bool handled = false; size_t slotSpan = baseobj->slotSpan(); - if (!AnalyzePoppedThis(cx, group, thisValue, ins, definitelyExecuted, - baseobj, initializerList, &accessedProperties, - &handled)) { + if (!AnalyzePoppedThis(cx, constraintInfo, group, thisValue, ins, + definitelyExecuted, baseobj, initializerList, + &accessedProperties, &handled)) { return false; } if (!handled) break; @@ -4009,7 +4019,6 @@ bool jit::AnalyzeNewScriptDefiniteProper // contingent on the correct frames being inlined. Add constraints to // invalidate the definite properties if additional functions could be // called at the inline frame sites. - Vector exitBlocks(cx); for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { // Inlining decisions made after the last new property was added to @@ -4018,9 +4027,9 @@ bool jit::AnalyzeNewScriptDefiniteProper if (MResumePoint* rp = block->callerResumePoint()) { if (block->numPredecessors() == 1 && block->getPredecessor(0) == rp->block()) { - JSScript* script = rp->block()->info().script(); - if (!AddClearDefiniteFunctionUsesInScript(cx, group, script, - block->info().script())) + JSScript* caller = rp->block()->info().script(); + JSScript* callee = block->info().script(); + if (!constraintInfo.addInliningConstraint(caller, callee)) return false; } } --- mozjs-60.8.0/js/src/jit/IonAnalysis.h +++ mozjs-60.8.0/js/src/jit/IonAnalysis.h @@ -161,8 +161,8 @@ MCompare* ConvertLinearInequality(TempAl const LinearSum& sum); MOZ_MUST_USE bool AnalyzeNewScriptDefiniteProperties( - JSContext* cx, HandleFunction fun, ObjectGroup* group, - HandlePlainObject baseobj, + JSContext* cx, DPAConstraintInfo& constraintInfo, HandleFunction fun, + ObjectGroup* group, HandlePlainObject baseobj, Vector* initializerList); MOZ_MUST_USE bool AnalyzeArgumentsUsage(JSContext* cx, JSScript* script); --- mozjs-60.8.0/js/src/vm/TypeInference.cpp +++ mozjs-60.8.0/js/src/vm/TypeInference.cpp @@ -2759,33 +2759,36 @@ class TypeConstraintClearDefiniteGetterS JSCompartment* maybeCompartment() override { return group->compartment(); } }; -bool js::AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, - ObjectGroup* group, - HandleId id) { +bool js::AddClearDefiniteGetterSetterForPrototypeChain( + JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, + HandleId id, bool* added) { /* * Ensure that if the properties named here could have a getter, setter or * a permanent property in any transitive prototype, the definite * properties get cleared from the group. */ + + *added = false; + RootedObject proto(cx, group->proto().toObjectOrNull()); while (proto) { ObjectGroup* protoGroup = JSObject::getGroup(cx, proto); if (!protoGroup) { - cx->recoverFromOutOfMemory(); return false; } - if (protoGroup->unknownProperties()) return false; + if (protoGroup->unknownProperties()) + return true; HeapTypeSet* protoTypes = protoGroup->getProperty(cx, proto, id); - if (!protoTypes || protoTypes->nonDataProperty() || - protoTypes->nonWritableProperty()) + if (!protoTypes) return false; - if (!protoTypes->addConstraint( - cx, - cx->typeLifoAlloc().new_( - group))) + if (protoTypes->nonDataProperty() || protoTypes->nonWritableProperty()) + return true; + if (!constraintInfo.addProtoConstraint(proto, id)) return false; proto = proto->staticPrototype(); } + + *added = true; return true; } @@ -3272,6 +3275,43 @@ struct DestroyTypeNewScript { } // namespace +bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) { + for (const ProtoConstraint& constraint : protoConstraints_) { + ObjectGroup* protoGroup = constraint.proto->group(); + + // Note: we rely on the group's type information being unchanged since + // AddClearDefiniteGetterSetterForPrototypeChain. + + bool unknownProperties = protoGroup->unknownProperties(); + MOZ_RELEASE_ASSERT(!unknownProperties); + + HeapTypeSet* protoTypes = + protoGroup->getProperty(cx, constraint.proto, constraint.id); + MOZ_RELEASE_ASSERT(protoTypes); + + MOZ_ASSERT(!protoTypes->nonDataProperty()); + MOZ_ASSERT(!protoTypes->nonWritableProperty()); + + if (!protoTypes->addConstraint( + cx, + cx->typeLifoAlloc().new_( + group))) { + ReportOutOfMemory(cx); + return false; + } + } + + for (const InliningConstraint& constraint : inliningConstraints_) { + if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller, + constraint.callee)) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} + bool TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force) { // Perform the new script properties analysis if necessary, returning @@ -3367,10 +3407,12 @@ bool TypeNewScript::maybeAnalyze(JSConte Vector initializerVector(cx); + DPAConstraintInfo constraintInfo(cx); + RootedPlainObject templateRoot(cx, templateObject()); RootedFunction fun(cx, function()); - if (!jit::AnalyzeNewScriptDefiniteProperties(cx, fun, group, templateRoot, - &initializerVector)) + if (!jit::AnalyzeNewScriptDefiniteProperties( + cx, constraintInfo, fun, group, templateRoot, &initializerVector)) return false; if (!group->newScript()) return true; @@ -3445,6 +3487,14 @@ bool TypeNewScript::maybeAnalyze(JSConte // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis // is needed. + + if (!constraintInfo.finishConstraints(cx, group)) { + return false; + } + if (!group->newScript()) { + return true; + } + group->addDefiniteProperties(cx, templateObject()->lastProperty()); destroyNewScript.group = nullptr; @@ -3465,6 +3515,16 @@ bool TypeNewScript::maybeAnalyze(JSConte cx, group->clasp(), protoRoot, initialFlags); if (!initialGroup) return false; + // Add the constraints. Use the initialGroup as group referenced by the + // constraints because that's the group that will have the TypeNewScript + // associated with it. See the detachNewScript and setNewScript calls below. + if (!constraintInfo.finishConstraints(cx, initialGroup)) { + return false; + } + if (!group->newScript()) { + return true; + } + initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty()); group->addDefiniteProperties(cx, prefixShape); --- mozjs-60.8.0/js/src/vm/TypeInference.h +++ mozjs-60.8.0/js/src/vm/TypeInference.h @@ -858,9 +858,58 @@ class TemporaryTypeSet : public TypeSet TypedArraySharedness* sharedness); }; -bool AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, - ObjectGroup* group, - HandleId id); +// Stack class to record information about constraints that need to be added +// after finishing the Definite Properties Analysis. When the analysis succeeds +// the |finishConstraints| method must be called to add the constraints to the +// TypeSets. +// +// There are two constraint types managed here: +// +// 1. Proto constraints for HeapTypeSets, to guard against things like getters +// and setters on the proto chain. +// +// 2. Inlining constraints for StackTypeSets, to invalidate when additional +// functions could be called at call sites where we inlined a function. +// +// This class uses bare GC-thing pointers because GC is suppressed when the +// analysis runs. +class MOZ_RAII DPAConstraintInfo { + struct ProtoConstraint { + JSObject* proto; + jsid id; + ProtoConstraint(JSObject* proto, jsid id) : proto(proto), id(id) {} + }; + struct InliningConstraint { + JSScript* caller; + JSScript* callee; + InliningConstraint(JSScript* caller, JSScript* callee) + : caller(caller), callee(callee) {} + }; + + JS::AutoCheckCannotGC nogc_; + Vector protoConstraints_; + Vector inliningConstraints_; + + public: + explicit DPAConstraintInfo(JSContext* cx) + : nogc_(cx), protoConstraints_(cx), inliningConstraints_(cx) {} + + DPAConstraintInfo(const DPAConstraintInfo&) = delete; + void operator=(const DPAConstraintInfo&) = delete; + + MOZ_MUST_USE bool addProtoConstraint(JSObject* proto, jsid id) { + return protoConstraints_.emplaceBack(proto, id); + } + MOZ_MUST_USE bool addInliningConstraint(JSScript* caller, JSScript* callee) { + return inliningConstraints_.emplaceBack(caller, callee); + } + + MOZ_MUST_USE bool finishConstraints(JSContext* cx, ObjectGroup* group); +}; + +bool AddClearDefiniteGetterSetterForPrototypeChain( + JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, + HandleId id, bool* added); bool AddClearDefiniteFunctionUsesInScript(JSContext* cx, ObjectGroup* group, JSScript* script,