From 7622e2b8071fdf5eb01f9494690e860e3f87bce2 Mon Sep 17 00:00:00 2001 From: Igor Sheludko Date: Wed, 13 Nov 2019 20:37:22 +0100 Subject: [Backport] CVE-2020-6395 - Out of bounds read in JavaScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual backport of patch originally reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/1910939: [builtins] Ensure constructor has a prototype slot Drive-by-cleanup: simplify related helper functions in CSA. Bug: chromium:1022855 Change-Id: Iea0e090e319365d11cdd16603d67d402968b851a Reviewed-by: Jüri Valdmann --- chromium/v8/src/builtins/base.tq | 16 ++++-- .../v8/src/builtins/builtins-constructor-gen.cc | 11 ++-- chromium/v8/src/codegen/code-stub-assembler.cc | 58 +++++++++------------- chromium/v8/src/codegen/code-stub-assembler.h | 28 +++++++++-- chromium/v8/src/diagnostics/objects-debug.cc | 3 ++ 5 files changed, 65 insertions(+), 51 deletions(-) diff --git a/chromium/v8/src/builtins/base.tq b/chromium/v8/src/builtins/base.tq index 07af1f441f8..24c355d6b3e 100644 --- a/chromium/v8/src/builtins/base.tq +++ b/chromium/v8/src/builtins/base.tq @@ -336,15 +336,13 @@ macro NewJSObject(implicit context: Context)(): JSObject { }; } -extern macro HasPrototypeSlot(JSFunction): bool; +type JSFunctionWithPrototypeSlot extends JSFunction; macro GetDerivedMap(implicit context: Context)( target: JSFunction, newTarget: JSReceiver): Map { try { - const constructor = Cast(newTarget) otherwise SlowPath; - if (!HasPrototypeSlot(constructor)) { - goto SlowPath; - } + const constructor = + Cast(newTarget) otherwise SlowPath; assert(IsConstructor(constructor)); const map = Cast(constructor.prototype_or_initial_map) otherwise SlowPath; @@ -1814,6 +1812,9 @@ extern macro HeapObjectToString(HeapObject): String labels CastError; extern macro HeapObjectToConstructor(HeapObject): Constructor labels CastError; +extern macro HeapObjectToJSFunctionWithPrototypeSlot(HeapObject): + JSFunctionWithPrototypeSlot + labels CastError; extern macro HeapObjectToHeapNumber(HeapObject): HeapNumber labels CastError; extern macro HeapObjectToSloppyArgumentsElements(HeapObject): @@ -1967,6 +1968,11 @@ Cast(o: HeapObject): Constructor return HeapObjectToConstructor(o) otherwise CastError; } +Cast(o: HeapObject): JSFunctionWithPrototypeSlot + labels CastError { + return HeapObjectToJSFunctionWithPrototypeSlot(o) otherwise CastError; +} + Cast(o: HeapObject): HeapNumber labels CastError { if (IsHeapNumber(o)) return %RawDownCast(o); diff --git a/chromium/v8/src/builtins/builtins-constructor-gen.cc b/chromium/v8/src/builtins/builtins-constructor-gen.cc index 767e626432e..56dc23e233e 100644 --- a/chromium/v8/src/builtins/builtins-constructor-gen.cc +++ b/chromium/v8/src/builtins/builtins-constructor-gen.cc @@ -182,15 +182,14 @@ compiler::TNode ConstructorBuiltinsAssembler::EmitFastNewObject( SloppyTNode context, SloppyTNode target, SloppyTNode new_target, Label* call_runtime) { // Verify that the new target is a JSFunction. - Label fast(this), end(this); - GotoIf(HasInstanceType(new_target, JS_FUNCTION_TYPE), &fast); - Goto(call_runtime); - - BIND(&fast); + Label end(this); + TNode new_target_func = + HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime); + // Fast path. // Load the initial map and verify that it's in fact a map. Node* initial_map = - LoadObjectField(new_target, JSFunction::kPrototypeOrInitialMapOffset); + LoadJSFunctionPrototypeOrInitialMap(new_target_func); GotoIf(TaggedIsSmi(initial_map), call_runtime); GotoIf(DoesntHaveInstanceType(initial_map, MAP_TYPE), call_runtime); diff --git a/chromium/v8/src/codegen/code-stub-assembler.cc b/chromium/v8/src/codegen/code-stub-assembler.cc index e4f35ddcc88..392221e8725 100644 --- a/chromium/v8/src/codegen/code-stub-assembler.cc +++ b/chromium/v8/src/codegen/code-stub-assembler.cc @@ -2608,42 +2608,38 @@ TNode CodeStubAssembler::IsGeneratorFunction( shared_function_info, SharedFunctionInfo::kFlagsOffset, MachineType::Uint32())); - return TNode::UncheckedCast(Word32Or( - Word32Or( - Word32Or( - Word32Equal(function_kind, - Int32Constant(FunctionKind::kAsyncGeneratorFunction)), - Word32Equal( - function_kind, - Int32Constant(FunctionKind::kAsyncConciseGeneratorMethod))), - Word32Equal(function_kind, - Int32Constant(FunctionKind::kGeneratorFunction))), - Word32Equal(function_kind, - Int32Constant(FunctionKind::kConciseGeneratorMethod)))); -} - -TNode CodeStubAssembler::HasPrototypeSlot(TNode function) { - return TNode::UncheckedCast(IsSetWord32( - LoadMapBitField(LoadMap(function)))); -} - -TNode CodeStubAssembler::HasPrototypeProperty(TNode function, - TNode map) { + // See IsGeneratorFunction(FunctionKind kind). + return IsInRange(function_kind, FunctionKind::kAsyncConciseGeneratorMethod, + FunctionKind::kConciseGeneratorMethod); +} + +TNode CodeStubAssembler::IsJSFunctionWithPrototypeSlot( + TNode object) { + // Only JSFunction maps may have HasPrototypeSlotBit set. + return TNode::UncheckedCast( + IsSetWord32(LoadMapBitField(LoadMap(object)))); +} + +void CodeStubAssembler::BranchIfHasPrototypeProperty( + TNode function, TNode function_map_bit_field, + Label* if_true, Label* if_false) { // (has_prototype_slot() && IsConstructor()) || // IsGeneratorFunction(shared()->kind()) uint32_t mask = Map::HasPrototypeSlotBit::kMask | Map::IsConstructorBit::kMask; - return TNode::UncheckedCast( - Word32Or(IsAllSetWord32(LoadMapBitField(map), mask), - IsGeneratorFunction(function))); + + GotoIf(IsAllSetWord32(function_map_bit_field, mask), if_true); + Branch(IsGeneratorFunction(function), if_true, if_false); } void CodeStubAssembler::GotoIfPrototypeRequiresRuntimeLookup( TNode function, TNode map, Label* runtime) { // !has_prototype_property() || has_non_instance_prototype() - GotoIfNot(HasPrototypeProperty(function, map), runtime); - GotoIf(IsSetWord32(LoadMapBitField(map)), - runtime); + TNode map_bit_field = LoadMapBitField(map); + Label next_check(this); + BranchIfHasPrototypeProperty(function, map_bit_field, &next_check, runtime); + BIND(&next_check); + GotoIf(IsSetWord32(map_bit_field), runtime); } Node* CodeStubAssembler::LoadJSFunctionPrototype(Node* function, @@ -13532,14 +13528,6 @@ TNode CodeStubAssembler::IsElementsKindLessThanOrEqual( return Int32LessThanOrEqual(target_kind, Int32Constant(reference_kind)); } -TNode CodeStubAssembler::IsElementsKindInRange( - TNode target_kind, ElementsKind lower_reference_kind, - ElementsKind higher_reference_kind) { - return Uint32LessThanOrEqual( - Int32Sub(target_kind, Int32Constant(lower_reference_kind)), - Int32Constant(higher_reference_kind - lower_reference_kind)); -} - Node* CodeStubAssembler::IsDebugActive() { Node* is_debug_active = Load( MachineType::Uint8(), diff --git a/chromium/v8/src/codegen/code-stub-assembler.h b/chromium/v8/src/codegen/code-stub-assembler.h index 3a5a6233889..0db31fdfb83 100644 --- a/chromium/v8/src/codegen/code-stub-assembler.h +++ b/chromium/v8/src/codegen/code-stub-assembler.h @@ -374,6 +374,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler return CAST(heap_object); } + TNode HeapObjectToJSFunctionWithPrototypeSlot( + TNode heap_object, Label* fail) { + GotoIfNot(IsJSFunctionWithPrototypeSlot(heap_object), fail); + return CAST(heap_object); + } + Node* MatchesParameterMode(Node* value, ParameterMode mode); #define PARAMETER_BINOP(OpName, IntPtrOpName, SmiOpName) \ @@ -727,6 +733,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode WordIsAligned(SloppyTNode word, size_t alignment); TNode WordIsPowerOfTwo(SloppyTNode value); + // Check if lower_limit <= value <= higher_limit. + template + TNode IsInRange(TNode value, U lower_limit, U higher_limit) { + DCHECK_LE(lower_limit, higher_limit); + STATIC_ASSERT(sizeof(U) <= kInt32Size); + return Uint32LessThanOrEqual(Int32Sub(value, Int32Constant(lower_limit)), + Int32Constant(higher_limit - lower_limit)); + } + #if DEBUG void Bind(Label* label, AssemblerDebugInfo debug_info); #endif // DEBUG @@ -1274,9 +1289,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode LoadJSArrayElementsMap(SloppyTNode kind, SloppyTNode native_context); - TNode HasPrototypeSlot(TNode function); + TNode IsJSFunctionWithPrototypeSlot(TNode object); TNode IsGeneratorFunction(TNode function); - TNode HasPrototypeProperty(TNode function, TNode map); + void BranchIfHasPrototypeProperty(TNode function, + TNode function_map_bit_field, + Label* if_true, Label* if_false); void GotoIfPrototypeRequiresRuntimeLookup(TNode function, TNode map, Label* runtime); // Load the "prototype" property of a JSFunction. @@ -2393,11 +2410,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler ElementsKind reference_kind); TNode IsElementsKindLessThanOrEqual(TNode target_kind, ElementsKind reference_kind); - // Check if reference_kind_a <= target_kind <= reference_kind_b + // Check if lower_reference_kind <= target_kind <= higher_reference_kind. TNode IsElementsKindInRange(TNode target_kind, ElementsKind lower_reference_kind, - ElementsKind higher_reference_kind); - + ElementsKind higher_reference_kind) { + return IsInRange(target_kind, lower_reference_kind, higher_reference_kind); + } // String helpers. // Load a character from a String (might flatten a ConsString). TNode StringCharCodeAt(SloppyTNode string, diff --git a/chromium/v8/src/diagnostics/objects-debug.cc b/chromium/v8/src/diagnostics/objects-debug.cc index 07634f3ca79..c5d1b25737f 100644 --- a/chromium/v8/src/diagnostics/objects-debug.cc +++ b/chromium/v8/src/diagnostics/objects-debug.cc @@ -691,6 +691,9 @@ void Map::MapVerify(Isolate* isolate) { .IsConsistentWithBackPointers()); SLOW_DCHECK(!FLAG_unbox_double_fields || layout_descriptor().IsConsistentWithMap(*this)); + // Only JSFunction maps have has_prototype_slot() bit set and constructible + // JSFunction objects must have prototype slot. + CHECK_IMPLIES(has_prototype_slot(), instance_type() == JS_FUNCTION_TYPE); if (!may_have_interesting_symbols()) { CHECK(!has_named_interceptor()); CHECK(!is_dictionary_map()); -- cgit v1.2.1