High chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Bug ClassType Confusion
Tracker479726070
Fix commit4508b5dfb26e (v8/v8) +11/-3
CISA KEVNot listed
CreditedChaoyuan Peng (@ret2happy)
Disclosed2026-02-03

Changed Functions

FunctionChangeNotes
switch
src/maglev/maglev-ir.h
modified

Files Changed

  • src/maglev/maglev-ir.cc
  • src/maglev/maglev-ir.h
From 4508b5dfb26e86f975fc57cf04350d67071fe98e Mon Sep 17 00:00:00 2001
From: Victor Gomes <victorgomes@chromium.org>
Date: Fri, 30 Jan 2026 15:18:32 +0100
Subject: [PATCH] [maglev] Module variables can be the hole

Module variables are lowered in Maglev to
LoadTaggedField(cell, Cell:kValueOffset).

Drive-by: order opcodes alphabetically in CanBeTheHoleValue.

Fixed: 479726070
Change-Id: I2be5752906cf2ec8fdb4df497724a4d9ad55648d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7534881
Auto-Submit: Victor Gomes <victorgomes@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105008}
---

diff --git a/src/maglev/maglev-ir.cc b/src/maglev/maglev-ir.cc
index 47c5e09..dd35ce7 100644
--- a/src/maglev/maglev-ir.cc
+++ b/src/maglev/maglev-ir.cc
@@ -669,6 +669,13 @@
   if (const RootConstant* cst = TryCast<RootConstant>()) {
     return ToTribool(cst->index() == RootIndex::kTheHoleValue);
   }
+  if (const LoadTaggedField* load = TryCast<LoadTaggedField>()) {
+    // Modules variables can be the hole.
+    if (load->offset() == Cell::kValueOffset) {
+      return Tribool::kMaybe;
+    }
+    return Tribool::kFalse;
+  }
   if (const LoadFixedArrayElement* load = TryCast<LoadFixedArrayElement>()) {
     if (load->load_type() != LoadType::kUnknown) {
       return Tribool::kFalse;
diff --git a/src/maglev/maglev-ir.h b/src/maglev/maglev-ir.h
index 11bc7d48..f4c0ff8 100644
--- a/src/maglev/maglev-ir.h
+++ b/src/maglev/maglev-ir.h
@@ -733,17 +733,18 @@
 
 constexpr bool CanBeTheHoleValue(Opcode opcode) {
   switch (opcode) {
-    case Opcode::kInitialValue:
-    case Opcode::kCallRuntime:
     // TODO(victorgomes): Should we have a list of builtins that could
     // return the hole?
     case Opcode::kCallBuiltin:
+    case Opcode::kCallRuntime:
     case Opcode::kGeneratorRestoreRegister:
-    case Opcode::kRootConstant:
+    case Opcode::kInitialValue:
     case Opcode::kLoadContextSlot:
     case Opcode::kLoadContextSlotNoCells:
     case Opcode::kLoadFixedArrayElement:
+    case Opcode::kLoadTaggedField:
     case Opcode::kPhi:
+    case Opcode::kRootConstant:
       return true;
     default:
       return false;
Loading diff…

Original Bug Report

reported by ha...@gmail.com

Hole leak in MaglevGraphBuilder

VULNERABILITY DETAILS

Summary

MaglevGraphBuilder::VisitThrowReferenceErrorIfHole incorrectly eliminates TDZ checks for module variables because CanBeTheHoleValue does not include LoadTaggedField in its whitelist. This causes the_hole_value (HOLE_TYPE) to leak into JS when accessing uninitialized module bindings, leading to type confusion. This issue is similar to crbug.com/450618029

Suggested Fix

Add Opcode::kLoadTaggedField to the CanBeTheHoleValue() whitelist, or special-case Cell::kValueOffset reads to return Tribool::kMaybe to prevent hole check elimination for module variable accesses.

Attached fix.diff which adds kLoadTaggedField to the CanBeTheHoleValue() whitelist:

diff --git a/src/maglev/maglev-ir.h b/src/maglev/maglev-ir.h
--- a/src/maglev/maglev-ir.h
+++ b/src/maglev/maglev-ir.h
@@ -734,6 +734,9 @@ constexpr bool CanBeTheHoleValue(Opcode opcode) {
   switch (opcode) {
     case Opcode::kInitialValue:
     case Opcode::kCallRuntime:
+    case Opcode::kLoadTaggedField:
     // TODO(victorgomes): Should we have a list of builtins that could
     // return the hole?
     case Opcode::kCallBuiltin:

Details

When accessing module variables, Ignition generates the following bytecode sequence:

  • LdaModuleVariable
  • ThrowReferenceErrorIfHole "<name>"
  • Return

The ThrowReferenceErrorIfHole bytecode is critical for TDZ enforcement and must be preserved to prevent the_hole_value from leaking.

In MaglevGraphBuilder::VisitLdaModuleVariable, module variable accesses are compiled to read from a Cell via LoadTaggedField(Cell::kValueOffset). The issue is that ValueNode::IsTheHole returns kFalse for LoadTaggedField nodes because CanBeTheHoleValue does not include Opcode::kLoadTaggedField in its whitelist:

constexpr bool CanBeTheHoleValue(Opcode opcode) {
  switch (opcode) {
    case Opcode::kInitialValue:
    case Opcode::kCallRuntime:
    // TODO(victorgomes): Should we have a list of builtins that could
    // return the hole?
    case Opcode::kCallBuiltin:
    case Opcode::kGeneratorRestoreRegister:
    case Opcode::kRootConstant:
    case Opcode::kLoadContextSlot:
    case Opcode::kLoadContextSlotNoCells:
    case Opcode::kLoadFixedArrayElement:
    case Opcode::kPhi:
      return true;
    default:
      return false;
  }
}

As a result, when MaglevGraphBuilder::VisitThrowReferenceErrorIfHole checks IsTheHole() and gets kFalse, it skips inserting the hole check entirely. Additionally, MaglevGraphOptimizer::VisitThrowReferenceErrorIfHole removes any existing ThrowReferenceErrorIfHole nodes when IsTheHole() == kFalse.

ReduceResult MaglevGraphBuilder::VisitThrowReferenceErrorIfHole() {
  // ThrowReferenceErrorIfHole <variable_name>
  compiler::NameRef name = GetRefOperand<Name>(0);
  ValueNode* value = GetAccumulator();
  switch (value->IsTheHole()) {
    case Tribool::kTrue:
      return BuildThrow(Throw::kThrowAccessedUninitializedVariable,
                        GetConstant(name));
    case Tribool::kFalse:
      return ReduceResult::Done();
    case Tribool::kMaybe:
      DCHECK(value->is_tagged());
      return AddNewNode<ThrowReferenceErrorIfHole>({value}, name);
  }
}

This causes the_hole_value to leak into JavaScript, which then triggers crashes when the value is used (e.g., in Map.delete() or JSON.stringify()).

VERSION

V8 commit: 5853269075baaf304cd5be09d941a6c3e9671847

V8 Version: 14.6.101

REPRODUCTION CASE

Build args on commit 5853269075baaf304cd5be09d941a6c3e9671847:

is_component_build = false
is_debug = false
v8_enable_backtrace = true
dcheck_always_on = true
is_asan=true
v8_static_library=true

Run ./d8 --module --allow-natives-syntax poc.js, you would observe the DCHECK failure as the stack.txt shows.

View on issue tracker