文章总结: CVE-2024-12053是WebAssembly类型混淆漏洞,由规范索引与相对索引混淆引发,结合沙箱逃逸技术可在Chrome中实现远程代码执行。漏洞已在Chromebuild134修复,影响版本为131-133。文档详细分析了TypeCanonicalizer机制和类型规范化过程,并提供了完整技术原理与PoC示例。 综合评分: 87 文章分类: 漏洞分析,WEB安全,应用安全,二进制安全,漏洞预警
WebAssembly 规范类型索引与相对类型索引混淆导致远程代码执行
幻泉之洲
2026年5月1日 09:05 北京
在小说阅读器读本章
去阅读
CVE-2024-12053 是一个WebAssembly类型混淆漏洞,根源在于规范索引和相对索引的混用。攻击者通过利用该漏洞进行类型混淆,再结合沙箱逃逸技术(issue 361862752),可以在Chrome中实现任意代码执行。本文深入分析了漏洞原理、利用方式,并给出了完整的PoC。
漏洞概述
CVE-2024-12053 是一个WebAssembly类型混淆漏洞,由规范索引(canonical index)和相对索引(relative index)的混淆引发。Chromium团队指出,该漏洞已在野外被利用,配合沙箱逃逸技术(issue 361862752)实现了远程代码执行。
注意:该漏洞并非由SSD实验室发现。
厂商回应
Chrome build 134 中已经修复了这个CVE。
来源
- 漏洞分析:SSD Lab韩国团队 Aaron Cho
受影响版本
- Chrome build 133, 132, 131
技术分析:CVE-2024-12053
WebAssembly 类型规范化
WebAssembly 用了一套类型系统来提升内存效率、保证类型安全。类型五花八门,各有各的用途。
类型规范化的目标是把散落在多个 WebAssembly 模块中的相同类型合并到一处,统一管理,省事又高效。
举个例子,WebAssembly 函数是按函数签名来区分的。一个接一个参数的和接两个参数的函数,就是不同的类型。
/* src/codegen/signature.h:17-19 */ // Describes the inputs and outputs of a function or call. template class Signature : public ZoneObject {
/* src/wasm/value-type.h:1175-1176 */ using FunctionSig = Signature; using CanonicalSig = Signature;
V8 里的 Signature 类处理函数签名。每个 WebAssembly 函数都得在模块的类型段里声明自己的签名。
;; test.wat (module (func (param i32)) (func (param i32 i32)) (func (param i32 i32)) )
上面这个模块定义了三个空函数。第一个接一个 i32,后两个各接两个 i32。
▲ 第二个和第三个函数签名相同,所以在类型段里只定义一次。同样,跨多个模块出现相同类型也能合并成一个规范类型。
/* src/wasm/canonical-types.h:41-52 */ // A singleton class, responsible for isorecursive canonicalization of wasm // types. // A recursive group is a subsequence of types explicitly marked in the type // section of a wasm module. Identical recursive groups have to be canonicalized // to a single canonical group. Respective types in two identical groups are // considered identical for all purposes. // Two groups are considered identical if they have the same shape, and all // type indices referenced in the same position in both groups reference: // – identical types, if those do not belong to the rec. group, // – types in the same relative position in the group, if those belong to the // rec. group. class TypeCanonicalizer {
类型规范化由 TypeCanonicalizer 这个单例类管理——运行时只有一个实例。类的注释写着它负责把相同的递归组规范化。在 WebAssembly 里,递归组就是一组类型。只有一个类型的递归组叫递归单例组。不在任何递归组里的单个类型,规范时也当成递归单例组处理。
TypeCanonicalizer 在 V8 初始化时构造。
/* src/wasm/canonical-types.cc:22 */ TypeCanonicalizer::TypeCanonicalizer() { AddPredefinedArrayTypes(); }
构造函数调用了 AddPredefinedArrayTypes()。
/* src/wasm/canonical-types.h:54-56 */ static constexpr CanonicalTypeIndex kPredefinedArrayI8Index{0}; static constexpr CanonicalTypeIndex kPredefinedArrayI16Index{1}; static constexpr uint32_t kNumberOfPredefinedTypes = 2;
/* src/wasm/canonical-types.cc:191-211 */ void TypeCanonicalizer::AddPredefinedArrayTypes() { static constexpr std::pair kPredefinedArrayTypes[] = {{kPredefinedArrayI8Index, {kWasmI8}}, {kPredefinedArrayI16Index, {kWasmI16}}}; for (auto [index, element_type] : kPredefinedArrayTypes) { DCHECK_EQ(index.index, canonical_singleton_groups_.size()); CanonicalSingletonGroup group; static constexpr bool kMutable = true; // TODO(jkummerow): Decide whether this should be final or nonfinal. static constexpr bool kFinal = true; static constexpr bool kShared = false; // TODO(14616): Fix this. static constexpr bool kNonRelativeSupertype = false; CanonicalArrayType* type = zone_.New(element_type, kMutable); group.type = CanonicalType(type, CanonicalTypeIndex{kNoSuperType}, kFinal, kShared, kNonRelativeSupertype); canonical_singleton_groups_.emplace(group, index); canonical_supertypes_.emplace_back(CanonicalTypeIndex{kNoSuperType}); DCHECK_LE(canonical_supertypes_.size(), kMaxCanonicalTypes); } }
AddPredefinedArrayTypes() 往 canonical_singleton_groups_ 里加了一些默认的规范类型。有两个预定义类型,索引分别是 0 和 1。
下一个规范类型是在创建 context 时由 WasmJs::Install() 加进去的。
/* src/wasm/wasm-js.cc:3207-3212 */ constexpr wasm::ValueType kWasmExceptionTagParams[] = { wasm::kWasmExternRef, }; constexpr wasm::FunctionSig kWasmExceptionTagSignature{ 0, arraysize(kWasmExceptionTagParams), kWasmExceptionTagParams}; } // namespace
/* src/wasm/wasm-js.cc:3475-3483 */ // Reset the JSTag’s canonical_type_index based on this Isolate’s // type_canonicalizer. DirectHandle js_tag_object( Cast(native_context->wasm_js_tag()), isolate); js_tag_object->set_canonical_type_index( wasm::GetWasmEngine() ->type_canonicalizer() ->AddRecursiveGroup(&kWasmExceptionTagSignature) .index);
WasmJs::Install() 把 js_tag_object 的签名规范化了。
这种情况下类型是函数签名,所以同时加到了 canonical_singleton_groups_ 和 canonical_function_sigs_ 里。加到 canonical_function_sigs_ 时,索引和 canonical_singleton_groups_ 里的一样,即使这是加进去的第一个函数签名。所以 canonical_function_sigs_ 的前两个槽位是空的。
规范化递归单例组
d8.file.execute(“v8/test/mjsunit/wasm/wasm-module-builder.js”);
let builder = new WasmModuleBuilder();
// recursive singleton group builder.startRecGroup(); let type = builder.nextTypeIndex(); builder.addType(makeSig([], [wasmRefType(type)])); builder.endRecGroup();
builder.instantiate();
这段 JavaScript 定义了一个 WebAssembly 模块,里面有一个递归单例组,函数签名没有参数,返回值是一个指向自身的引用类型。实例化时,解码类型段由 ModuleDecoderImpl::DecodeTypeSection() 处理。
/* src/wasm/wasm-limits.h:29 */ constexpr size_t kV8MaxWasmTypes = 1’000’000;
/* src/wasm/module-decoder-impl.h:625-627 */ void DecodeTypeSection() { TypeCanonicalizer* type_canon = GetTypeCanonicalizer(); uint32_t types_count = consume_count(“types count”, kV8MaxWasmTypes);
types_count 是递归组的个数,不是单独类型的个数,不能超过 kV8MaxWasmTypes。
/* src/wasm/wasm-constants.h:65 */ constexpr uint8_t kWasmRecursiveTypeGroupCode = 0x4e;
/* src/wasm/module-decoder-impl.h:629-662 */ for (uint32_t i = 0; ok() && i < types_count; i++) { if (tracer_) tracer_->TypeOffset(pc_ – start_); uint8_t kind = read_u8(pc(), “type kind”); size_t initial_size = module_->types.size(); if (kind == kWasmRecursiveTypeGroupCode) { module_->is_wasm_gc = true; uint32_t rec_group_offset = pc_offset(); consume_bytes(1, “rec. group definition”, tracer_); if (tracer_) tracer_->NextLine(); uint32_t group_size = consume_count(“recursive group size”, kV8MaxWasmTypes); if (tracer_) tracer_->RecGroupOffset(rec_group_offset, group_size); if (initial_size + group_size > kV8MaxWasmTypes) { errorf(pc(), “Type definition count exceeds maximum %zu”, kV8MaxWasmTypes); return; } // We need to resize types before decoding the type definitions in this // group, so that the correct type size is visible to type definitions. module_->types.resize(initial_size + group_size); module_->isorecursive_canonical_type_ids.resize(initial_size + group_size); for (uint32_t j = 0; j < group_size; j++) { if (tracer_) tracer_->TypeOffset(pc_offset()); TypeDefinition type = consume_subtype_definition(initial_size + j); module_->types[initial_size + j] = type; } if (failed()) return; type_canon->AddRecursiveGroup(module_.get(), group_size); if (tracer_) { tracer_->Description(“end of rec. group”); tracer_->NextLine(); } } else {
builder.startRecGroup(); 把组类型设为 kWasmRecursiveTypeGroupCode。这时类型由 TypeCanonicalizer::AddRecursiveGroup() 处理。
/* src/wasm/canonical-types.cc:30-41 */ void TypeCanonicalizer::AddRecursiveGroup(WasmModule* module, uint32_t size) { AddRecursiveGroup(module, size, static_cast(module->types.size() – size)); }
void TypeCanonicalizer::AddRecursiveGroup(WasmModule* module, uint32_t size, uint32_t start_index) { if (size == 0) return; // If the caller knows statically that {size == 1}, it should have called // {AddRecursiveSingletonGroup} directly. For cases where this is not // statically determined we add this dispatch here. if (size == 1) return AddRecursiveSingletonGroup(module, start_index);
递归单例组只有一个类型,所以 AddRecursiveGroup() 调用了 AddRecursiveSingletonGroup()。
/* src/wasm/canonical-types.cc:104-111 */ void TypeCanonicalizer::AddRecursiveSingletonGroup(WasmModule* module, uint32_t start_index) { base::MutexGuard guard(&mutex_); DCHECK_GT(module->types.size(), start_index); CanonicalTypeIndex canonical_index = AddRecursiveGroup( CanonicalizeTypeDef(module, module->types[start_index], start_index)); module->isorecursive_canonical_type_ids[start_index] = canonical_index; }
AddRecursiveSingletonGroup() 调用 CanonicalizeTypeDef() 为该类型创建一个 TypeCanonicalizer::CanonicalType 对象。
/* src/wasm/canonical-types.cc:271-310 */ switch (type.kind) { case TypeDefinition::kFunction: { const FunctionSig* original_sig = type.function_sig; CanonicalSig::Builder builder(&zone_, original_sig->return_count(), original_sig->parameter_count()); for (ValueType ret : original_sig->returns()) { builder.AddReturn( CanonicalizeValueType(module, ret, recursive_group_start)); } for (ValueType param : original_sig->parameters()) { builder.AddParam( CanonicalizeValueType(module, param, recursive_group_start)); } return CanonicalType(builder.Get(), supertype, type.is_final, type.is_shared, is_relative_supertype); } case TypeDefinition::kStruct: { const StructType* original_type = type.struct_type; CanonicalStructType::Builder builder(&zone_, original_type->field_count()); for (uint32_t i = 0; i < original_type->field_count(); i++) { builder.AddField(CanonicalizeValueType(module, original_type->field(i), recursive_group_start), original_type->mutability(i), original_type->field_offset(i)); } builder.set_total_fields_size(original_type->total_fields_size()); return CanonicalType( builder.Build(CanonicalStructType::Builder::kUseProvidedOffsets), supertype, type.is_final, type.is_shared, is_relative_supertype); } case TypeDefinition::kArray: { CanonicalValueType element_type = CanonicalizeValueType( module, type.array_type->element_type(), recursive_group_start); CanonicalArrayType* array_type = zone_.New( element_type, type.array_type->mutability()); return CanonicalType(array_type, supertype, type.is_final, type.is_shared, is_relative_supertype); } }
CanonicalizeTypeDef() 会为所有用到的类型调用 CanonicalizeValueType()。
/* src/wasm/value-type.h:642-644 */ constexpr bool has_index() const { return is_rtt() || (is_object_reference() && heap_type().is_index()); }
/* src/wasm/canonical-types.cc:213-216 */ CanonicalValueType TypeCanonicalizer::CanonicalizeValueType( const WasmModule* module, ValueType type, uint32_t recursive_group_start) const { if (!type.has_index()) return CanonicalValueType{type};
CanonicalizeValueType() 在 type.has_index() 为 false 时直接返回 CanonicalValueType{type}。has_index() 在类型是 RTT 或引用了其他堆类型时返回 true。
/* src/wasm/canonical-types.cc:218-222 */ return type.ref_index().index >= recursive_group_start ? CanonicalValueType::WithRelativeIndex( type.kind(), type.ref_index().index – recursive_group_start) : CanonicalValueType::FromIndex( type.kind(), module->canonical_type_id(type.ref_index()));
type.ref_index().index >= recursive_group_start 表示类型引用了同一个递归组里的另一个类型。这时可以用相对索引,所以 CanonicalizeValueType() 通过 CanonicalValueType::WithRelativeIndex() 返回了一个 CanonicalValueType 对象。
/* src/wasm/value-type.h:1059-1064 */ static constexpr CanonicalValueType WithRelativeIndex(ValueKind kind, uint32_t index) { return CanonicalValueType{ ValueTypeBase(KindField::encode(kind) | HeapTypeField::encode(index) | CanonicalRelativeField::encode(true))}; }
CanonicalValueType::WithRelativeIndex() 把 KindField 设为 kind,HeapTypeField 设为 index,CanonicalRelativeField 设为 true——表示这个索引是递归组内的相对索引。然后创建并返回 CanonicalValueType 对象。
/* src/wasm/canonical-types.cc:104-111 */ void TypeCanonicalizer::AddRecursiveSingletonGroup(WasmModule* module, uint32_t start_index) { base::MutexGuard guard(&mutex_); DCHECK_GT(module->types.size(), start_index); CanonicalTypeIndex canonical_index = AddRecursiveGroup( CanonicalizeTypeDef(module, module->types[start_index], start_index)); module->isorecursive_canonical_type_ids[start_index] = canonical_index; }
CanonicalizeTypeDef() 返回的 CanonicalType 对象被传给 AddRecursiveGroup()。
/* src/wasm/canonical-types.cc:151-181 */ CanonicalTypeIndex TypeCanonicalizer::AddRecursiveGroup(CanonicalType type) { DCHECK(!mutex_.TryLock()); // The caller must hold the mutex. CanonicalSingletonGroup group{type}; if (CanonicalTypeIndex index = FindCanonicalGroup(group); index.valid()) { // Make sure this signature can be looked up later. DCHECK_IMPLIES(type.kind == CanonicalType::kFunction, canonical_function_sigs_.count(index)); return index; } static_assert(kMaxCanonicalTypes < (1 << CanonicalTypeIndex::kBits)); CanonicalTypeIndex index(canonical_singleton_groups_.size()); // Check that this canonical ID is not used yet. DCHECK(std::none_of(canonical_singleton_groups_.begin(), canonical_singleton_groups_.end(), [=](auto& entry) { return entry.second == index; })); DCHECK(std::none_of(canonical_groups_.begin(), canonical_groups_.end(), [=](auto& entry) { return entry.second == index; })); canonical_singleton_groups_.emplace(group, index); // Compute the canonical index of the supertype: If it is relative, we // need to add {canonical_index}. canonical_supertypes_.push_back( type.is_relative_supertype ? CanonicalTypeIndex{type.supertype.index + index.index} : type.supertype); if (type.kind == CanonicalType::kFunction) { const CanonicalSig* sig = type.function_sig; CHECK(canonical_function_sigs_.emplace(index, sig).second); } CheckMaxCanonicalIndex(); return index; }
AddRecursiveGroup() 创建一个包含该类型的单例组,加到 canonical_singleton_groups_。如果类型是函数签名,同时加到 canonical_function_sigs_。
规范化单个类型
/* src/wasm/wasm-constants.h:58-65 */ // Binary encoding of type definitions. constexpr uint8_t kSharedFlagCode = 0x65; constexpr uint8_t kWasmFunctionTypeCode = 0x60; constexpr uint8_t kWasmStructTypeCode = 0x5f; constexpr uint8_t kWasmArrayTypeCode = 0x5e; constexpr uint8_t kWasmSubtypeCode = 0x50; constexpr uint8_t kWasmSubtypeFinalCode = 0x4f; constexpr uint8_t kWasmRecursiveTypeGroupCode = 0x4e;
在 ModuleDecoderImpl::DecodeTypeSection() 中,如果没有 builder.startRecGroup();,kind 就设为上面的类型定义之一。
d8.file.execute(“v8/test/mjsunit/wasm/wasm-module-builder.js”);
let builder = new WasmModuleBuilder();
// single type (treated similarly to recursive singleton group) let type = builder.nextTypeIndex(); builder.addType(makeSig([], [wasmRefType(type)]));
builder.instantiate();
这里类型是函数签名,所以 kind 设为 kWasmFunctionTypeCode。
/* src/wasm/module-decoder-impl.h:662-677 */ } else { if (tracer_) tracer_->TypeOffset(pc_offset()); if (initial_size + 1 > kV8MaxWasmTypes) { errorf(pc(), “Type definition count exceeds maximum %zu”, kV8MaxWasmTypes); return; } // Similarly to above, we need to resize types for a group of size 1. module_->types.resize(initial_size + 1); module_->isorecursive_canonical_type_ids.resize(initial_size + 1); TypeDefinition type = consume_subtype_definition(initial_size); if (ok()) { module_->types[initial_size] = type; type_canon->AddRecursiveSingletonGroup(module_.get()); } }
kind 不是 kWasmRecursiveTypeGroupCode,所以 DecodeTypeSection() 直接调用了 AddRecursiveSingletonGroup()。
/* src/wasm/canonical-types.cc:99-111 */ void TypeCanonicalizer::AddRecursiveSingletonGroup(WasmModule* module) { uint32_t start_index = static_cast(module->types.size() – 1); return AddRecursiveSingletonGroup(module, start_index); }
void TypeCanonicalizer::AddRecursiveSingletonGroup(WasmModule* module,
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《WebAssembly 规范类型索引与相对类型索引混淆导致远程代码执行》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论