High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in V8
DescriptionUse after free in V8
ComponentV8
Sub-componentDeserializer
Bug ClassUAF
Tracker568831769
Fix commit9d9ccafe3b95 (v8/v8) +242/-51
CISA KEVNot listed
CreditedXinyang Ge
Disclosed2026-10-15

Changed Functions

FunctionChangeNotes
if
src/snapshot/code-serializer.cc
modified
CodeSerializer
src/snapshot/code-serializer.h
modified
SerializedCodeData
src/snapshot/code-serializer.h
modified
TEST_F
test/unittests/api/deserialize-unittest.cc
modified

Files Changed

  • test/unittests/api/deserialize-unittest.cc
  • src/codegen/compiler.cc
  • src/snapshot/code-serializer.h
  • src/codegen/compiler.h
  • src/snapshot/code-serializer.cc
From 9d9ccafe3b95e4658806c1d7514257d6042a1ba4 Mon Sep 17 00:00:00 2001
From: Leszek Swirski <[email protected]>
Date: Wed, 07 Oct 2026 16:52:46 +0200
Subject: [PATCH] [deserializer] Check source hash before starting BG merge

Off-thread code cache deserialization happens before source text is
available, so we can't verify the cache's source hash up front. When
source text becomes available, SourceTextAvailable previously set up a
background merge with a matching Script from the compilation cache
without checking whether the serialized code cache's source hash
(including module vs. classic origin options) matched the incoming
script. If a module code cache was handed to a classic script load of
the same URL, the background merge would merge incompatible scripts
before FinishOffThreadDeserialize rejected the cache.

Check the cached data's source hash via
CodeSerializer::NotifySourceTextAvailable before setting up the
background merge task, and record the result in
OffThreadDeserializeData::source_sanity_check_result so
FinishOffThreadDeserialize does not recompute it. Populate
OffThreadDeserializeData in-place in StartDeserializeOffThread and avoid
reading data->sanity_check_result or mutating cached_data->Reject() in
NotifySourceTextAvailable so the main-thread source check cannot race
with or be clobbered by concurrent background deserialization.

TAG=agy
CONV=d5ba0f6d-8783-4ff9-b6c1-1b9dffa490f4

Fixed: 568831769
Change-Id: Id8d350af7ef0a4f52fb954ad16902b1cfab364ea
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8526067
Auto-Submit: Leszek Swirski <[email protected]>
Reviewed-by: Toon Verwaest <[email protected]>
Commit-Queue: Toon Verwaest <[email protected]>
Cr-Commit-Position: refs/heads/main@{#110449}
---
diff --git a/src/codegen/compiler.cc b/src/codegen/compiler.cc
index 6e4bf13..d1b2bec 100644
--- a/src/codegen/compiler.cc
+++ b/src/codegen/compiler.cc
@@ -2875,8 +2875,8 @@
   LocalHandleScope handle_scope(&isolate);
 
   DirectHandle<SharedFunctionInfo> inner_result;
-  off_thread_data_ =
-      CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_);
+  CodeSerializer::StartDeserializeOffThread(&isolate, &cached_data_,
+                                            &off_thread_data_);
   if (off_thread_data_.HasResult()) {
     Tagged<Script> script = *off_thread_data_.GetOnlyScript(isolate.heap());
     script_type_timer.set_histogram(
@@ -2898,6 +2898,11 @@
     Isolate* isolate, Handle<String> source_text,
     const ScriptDetails& script_details) {
   DCHECK_EQ(isolate, isolate_for_local_isolate_);
+  if (!CodeSerializer::NotifySourceTextAvailable(isolate, &off_thread_data_,
+                                                 &cached_data_, source_text,
+                                                 script_details)) {
+    return;
+  }
   LanguageMode language_mode = construct_language_mode(v8_flags.use_strict);
   background_merge_task_.SetUpOnMainThread(isolate, source_text, script_details,
                                            language_mode);
diff --git a/src/codegen/compiler.h b/src/codegen/compiler.h
index af0057d..003b30a 100644
--- a/src/codegen/compiler.h
+++ b/src/codegen/compiler.h
@@ -722,7 +722,7 @@
  private:
   Isolate* isolate_for_local_isolate_;
   AlignedCachedData cached_data_;
-  CodeSerializer::OffThreadDeserializeData off_thread_data_;
+  OffThreadDeserializeData off_thread_data_;
   BackgroundMergeTask background_merge_task_;
   TimedHistogram* timer_;
   int64_t background_time_in_microseconds_ = 0;
diff --git a/src/snapshot/code-serializer.cc b/src/snapshot/code-serializer.cc
index 9dcff86..e1bfe5d 100644
--- a/src/snapshot/code-serializer.cc
+++ b/src/snapshot/code-serializer.cc
@@ -346,8 +346,8 @@
     LocalIsolate local_isolate(isolate_, ThreadKind::kBackground);
     UnparkedScope unparked_scope(&local_isolate);
     LocalHandleScope handle_scope(&local_isolate);
-    off_thread_data_ =
-        CodeSerializer::StartDeserializeOffThread(&local_isolate, cached_data_);
+    CodeSerializer::StartDeserializeOffThread(&local_isolate, cached_data_,
+                                              &off_thread_data_);
   }
 
   MaybeDirectHandle<SharedFunctionInfo> Finalize(
@@ -361,7 +361,7 @@
  private:
   Isolate* isolate_;
   AlignedCachedData* cached_data_;
-  CodeSerializer::OffThreadDeserializeData off_thread_data_;
+  OffThreadDeserializeData off_thread_data_;
 };
 
 void FinalizeDeserialization(Isolate* isolate,
@@ -555,8 +555,7 @@
   return scope.CloseAndEscape(result);
 }
 
-DirectHandle<Script> CodeSerializer::OffThreadDeserializeData::GetOnlyScript(
-    LocalHeap* heap) {
+DirectHandle<Script> OffThreadDeserializeData::GetOnlyScript(LocalHeap* heap) {
   std::unique_ptr<PersistentHandles> previous_persistent_handles =
       heap->DetachPersistentHandles();
   heap->AttachPersistentHandles(std::move(persistent_handles));
@@ -574,32 +573,47 @@
   return script;
 }
 
-CodeSerializer::OffThreadDeserializeData
-CodeSerializer::StartDeserializeOffThread(LocalIsolate* local_isolate,
-                                          AlignedCachedData* cached_data) {
-  OffThreadDeserializeData result;
-
+void CodeSerializer::StartDeserializeOffThread(LocalIsolate* local_isolate,
+                                               AlignedCachedData* cached_data,
+                                               OffThreadDeserializeData* data) {
   DCHECK(!local_isolate->heap()->HasPersistentHandles());
 
   const SerializedCodeData scd =
       SerializedCodeData::FromCachedDataWithoutSource(
-          local_isolate, cached_data, &result.sanity_check_result);
-  if (result.sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
+          local_isolate, cached_data, &data->sanity_check_result);
+  if (data->sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
     // Exit early but don't report yet, we'll re-check this when finishing on
     // the main thread
     DCHECK(cached_data->rejected());
-    return result;
+    return;
   }
 
   MaybeDirectHandle<SharedFunctionInfo> local_maybe_result =
       OffThreadObjectDeserializer::DeserializeSharedFunctionInfo(
-          local_isolate, &scd, &result.scripts);
+          local_isolate, &scd, &data->scripts);
 
-  result.maybe_result =
+  data->maybe_result =
       local_isolate->heap()->NewPersistentMaybeHandle(local_maybe_result);
-  result.persistent_handles = local_isolate->heap()->DetachPersistentHandles();
+  data->persistent_handles = local_isolate->heap()->DetachPersistentHandles();
+}
 
-  return result;
+bool CodeSerializer::NotifySourceTextAvailable(
+    Isolate* isolate, OffThreadDeserializeData* data,
+    const AlignedCachedData* cached_data, DirectHandle<String> source,
+    const ScriptDetails& script_details) {
+  HandleScope scope(isolate);
+
+  // Only check the source hash against the immutable cached_data header and
+  // write the main-thread-only data->source_sanity_check_result field here. Do
+  // not read/write data->sanity_check_result or mutate cached_data->Reject(),
+  // as StartDeserializeOffThread may still be running concurrently on a
+  // background thread. Full rejection and histogram reporting happen in
+  // FinishOffThreadDeserialize.
+  SerializedCodeData scd(cached_data);
+  SerializedCodeSanityCheckResult sanity_check_result =
+      scd.SanityCheckJustSource(isolate, source, script_details);
+  data->source_sanity_check_result = sanity_check_result;
+  return sanity_check_result == SerializedCodeSanityCheckResult::kSuccess;
 }
 
 MaybeDirectHandle<SharedFunctionInfo>
@@ -615,11 +629,6 @@
 
   HandleScope scope(isolate);
 
-  DirectHandle<FixedArray> wrapped_arguments;
-  if (!script_details.wrapped_arguments.is_null()) {
-    wrapped_arguments = script_details.wrapped_arguments.ToHandleChecked();
-  }
-
   // Do a source sanity check now that we have the source. It's important for
   // FromPartiallySanityCheckedCachedData call that the sanity_check_result
   // holds the result of the off-thread sanity check.
@@ -627,11 +636,9 @@
       data.sanity_check_result;
   const SerializedCodeData scd =
       SerializedCodeData::FromPartiallySanityCheckedCachedData(
-          cached_data,
-          SerializedCodeData::SourceHash(source, wrapped_arguments,
-                                         script_details.origin_options,
-                                         isolate),
+          isolate, cached_data, data, source, script_details,
           &sanity_check_result);
+  USE(scd);
   if (sanity_check_result != SerializedCodeSanityCheckResult::kSuccess) {
     // The only case where the deserialization result could exist despite a
     // check failure is on a source mismatch, since we can't test for this
@@ -773,6 +780,17 @@
 }
 
 SerializedCodeSanityCheckResult SerializedCodeData::SanityCheckJustSource(
+    Isolate* isolate, DirectHandle<String> source,
+    const ScriptDetails& script_details) const {
+  DirectHandle<FixedArray> wrapped_arguments;
+  if (!script_details.wrapped_arguments.is_null()) {
+    wrapped_arguments = script_details.wrapped_arguments.ToHandleChecked();
+  }
+  return SanityCheckJustSource(SourceHash(
+      source, wrapped_arguments, script_details.origin_options, isolate));
+}
+
+SerializedCodeSanityCheckResult SerializedCodeData::SanityCheckJustSource(
     SourceHash expected_source_hash) const {
   if (size_ < kHeaderSize) {
     return SerializedCodeSanityCheckResult::kInvalidHeader;
@@ -918,7 +936,7 @@
   return base::Vector<const uint8_t>(payload, length);
 }
 
-SerializedCodeData::SerializedCodeData(AlignedCachedData* data)
+SerializedCodeData::SerializedCodeData(const AlignedCachedData* data)
     : SerializedData(const_cast<uint8_t*>(data->data()), data->length()) {}
 
 SerializedCodeData SerializedCodeData::FromCachedData(
@@ -953,12 +971,14 @@
 }
 
 SerializedCodeData SerializedCodeData::FromPartiallySanityCheckedCachedData(
-    AlignedCachedData* cached_data, SourceHash expected_source_hash,
+    Isolate* isolate, AlignedCachedData* cached_data,
+    const OffThreadDeserializeData& data, DirectHandle<String> source,
+    const ScriptDetails& script_details,
     SerializedCodeSanityCheckResult* rejection_result) {
-  DisallowGarbageCollection no_gc;
   // The previous call to FromCachedDataWithoutSource may have already rejected
   // the cached data, so reuse the previous rejection result if it's not a
   // success.
+  *rejection_result = data.sanity_check_result;
   if (*rejection_result != SerializedCodeSanityCheckResult::kSuccess) {
     // FromCachedDataWithoutSource doesn't check the source, so there can't be
     // a source mismatch.
@@ -968,7 +988,14 @@
     return SerializedCodeData(nullptr, 0);
   }
   SerializedCodeData scd(cached_data);
-  *rejection_result = scd.SanityCheckJustSource(expected_source_hash);
+  if (data.source_sanity_check_result.has_value()) {
+    DCHECK_EQ(*data.source_sanity_check_result,
+              scd.SanityCheckJustSource(isolate, source, script_details));
+    *rejection_result = *data.source_sanity_check_result;
+  } else {
+    *rejection_result =
+        scd.SanityCheckJustSource(isolate, source, script_details);
+  }
   if (*rejection_result != SerializedCodeSanityCheckResult::kSuccess) {
     // This check only checks the source, so the only possible failure is a
     // source mismatch.
diff --git a/src/snapshot/code-serializer.h b/src/snapshot/code-serializer.h
index 2caad19..315751a 100644
--- a/src/snapshot/code-serializer.h
+++ b/src/snapshot/code-serializer.h
@@ -6,6 +6,7 @@
 #define V8_SNAPSHOT_CODE_SERIALIZER_H_
 
 #include <array>
+#include <optional>
 
 #include "src/base/macros.h"
 #include "src/codegen/script-details.h"
@@ -62,6 +63,22 @@
 
 class CodeSerializer;
 
+struct OffThreadDeserializeData {
+ public:
+  bool HasResult() const { return !maybe_result.is_null(); }
+  DirectHandle<Script> GetOnlyScript(LocalHeap* heap);
+
+ private:
+  friend class CodeSerializer;
+  friend class SerializedCodeData;
+  MaybeIndirectHandle<SharedFunctionInfo> maybe_result;
+  std::vector<IndirectHandle<Script>> scripts;
+  std::unique_ptr<PersistentHandles> persistent_handles;
+  SerializedCodeSanityCheckResult sanity_check_result =
+      SerializedCodeSanityCheckResult::kSuccess;
+  std::optional<SerializedCodeSanityCheckResult> source_sanity_check_result;
+};
+
 // Wrapper around ScriptData to provide code-serializer-specific functionality.
 class SerializedCodeData : public SerializedData {
  public:
@@ -122,7 +139,9 @@
   // FromCachedDataWithoutSource. The rejection result from that call should be
   // passed into this one.
   static SerializedCodeData FromPartiallySanityCheckedCachedData(
-      AlignedCachedData* cached_data, SourceHash expected_source_hash,
+      Isolate* isolate, AlignedCachedData* cached_data,
+      const OffThreadDeserializeData& data, DirectHandle<String> source,
+      const ScriptDetails& script_details,
       SerializedCodeSanityCheckResult* rejection_result);
 
   // Used when producing.
@@ -135,7 +154,8 @@
   base::Vector<const uint8_t> Payload() const;
 
  private:
-  explicit SerializedCodeData(AlignedCachedData* data);
+  friend class CodeSerializer;
+  explicit SerializedCodeData(const AlignedCachedData* data);
   SerializedCodeData(const uint8_t* data, int size)
       : SerializedData(const_cast<uint8_t*>(data), size) {}
 
@@ -151,6 +171,9 @@
       uint32_t expected_ro_snapshot_checksum,
       SourceHash expected_source_hash) const;
   SerializedCodeSanityCheckResult SanityCheckJustSource(
+      Isolate* isolate, DirectHandle<String> source,
+      const ScriptDetails& script_details) const;
+  SerializedCodeSanityCheckResult SanityCheckJustSource(
       SourceHash expected_source_hash) const;
   SerializedCodeSanityCheckResult SanityCheckWithoutSource(
       uint32_t expected_ro_snapshot_checksum) const;
@@ -160,19 +183,6 @@
  public:
   using SourceHash = SerializedCodeData::SourceHash;
 
-  struct OffThreadDeserializeData {
-   public:
-    bool HasResult() const { return !maybe_result.is_null(); }
-    DirectHandle<Script> GetOnlyScript(LocalHeap* heap);
-
-   private:
-    friend class CodeSerializer;
-    MaybeIndirectHandle<SharedFunctionInfo> maybe_result;
-    std::vector<IndirectHandle<Script>> scripts;
-    std::unique_ptr<PersistentHandles> persistent_handles;
-    SerializedCodeSanityCheckResult sanity_check_result;
-  };
-
   CodeSerializer(const CodeSerializer&) = delete;
   CodeSerializer& operator=(const CodeSerializer&) = delete;
   V8_EXPORT_PRIVATE static ScriptCompiler::CachedData* Serialize(
@@ -186,9 +196,14 @@
               DirectHandle<String> source, const ScriptDetails& script_details,
               MaybeDirectHandle<Script> maybe_cached_script = {});
 
-  V8_WARN_UNUSED_RESULT static OffThreadDeserializeData
-  StartDeserializeOffThread(LocalIsolate* isolate,
-                            AlignedCachedData* cached_data);
+  static void StartDeserializeOffThread(LocalIsolate* isolate,
+                                        AlignedCachedData* cached_data,
+                                        OffThreadDeserializeData* data);
+
+  V8_WARN_UNUSED_RESULT static bool NotifySourceTextAvailable(
+      Isolate* isolate, OffThreadDeserializeData* data,
+      const AlignedCachedData* cached_data, DirectHandle<String> source,
+      const ScriptDetails& script_details);
 
   V8_WARN_UNUSED_RESULT static MaybeDirectHandle<SharedFunctionInfo>
   FinishOffThreadDeserialize(
diff --git a/test/unittests/api/deserialize-unittest.cc b/test/unittests/api/deserialize-unittest.cc
index ecd607e..fb9fb99 100644
--- a/test/unittests/api/deserialize-unittest.cc
+++ b/test/unittests/api/deserialize-unittest.cc
@@ -1333,4 +1333,148 @@
   }
 }
 
+TEST_F(MergeDeserializedCodeTest, ModuleCacheClassicScriptMismatch) {
+  i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
+      true;
+  std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
+  IsolateAndContextScope scope(this);
+  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
+  PersistentScriptOrigin default_origin(
+      isolate(), NewString("http://example.com/test.js"));
+
+  constexpr char kPolyglotSource[] =
+      "let L0 = 1;\n"
+      "var z, o;\n"
+      "function g() { throw 1; }\n"
+      "function K(a, b) { var t = globalThis.top; (t.__keep = t.__keep || "
+      "[]).push(a, b); }\n"
+      "z = await /1; function lazyOuter() { function inner() {} } { try { g() "
+      "} catch /* 1, o = { a: function () {}, 'NAMENAMENAMENAME' //*/\n"
+      "(e) { K(() => e, function later() { return e }) } }\n";
+
+  // Setup: define `await` and `top` in global scope for classic script parse.
+  {
+    v8::HandleScope handle_scope(isolate());
+    Local<Script> setup =
+        Script::Compile(
+            context(), NewString("var await = 6; globalThis.top = globalThis;"))
+            .ToLocalChecked();
+    CHECK(!setup->Run(context()).IsEmpty());
+  }
+
+  // Step 1: Compile and run as a classic script, populating the Isolate
+  // compilation cache and retaining catch-block closures in globalThis.__keep.
+  // Also compile as a module with the same resource name and create a module
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/unittests/api/deserialize-unittest.cc b/test/unittests/api/deserialize-unittest.cc
index ecd607e..fb9fb99 100644
--- a/test/unittests/api/deserialize-unittest.cc
+++ b/test/unittests/api/deserialize-unittest.cc
@@ -1333,4 +1333,148 @@
   }
 }
 
+TEST_F(MergeDeserializedCodeTest, ModuleCacheClassicScriptMismatch) {
+  i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
+      true;
+  std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
+  IsolateAndContextScope scope(this);
+  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
+  PersistentScriptOrigin default_origin(
+      isolate(), NewString("http://example.com/test.js"));
+
+  constexpr char kPolyglotSource[] =
+      "let L0 = 1;\n"
+      "var z, o;\n"
+      "function g() { throw 1; }\n"
+      "function K(a, b) { var t = globalThis.top; (t.__keep = t.__keep || "
+      "[]).push(a, b); }\n"
+      "z = await /1; function lazyOuter() { function inner() {} } { try { g() "
+      "} catch /* 1, o = { a: function () {}, 'NAMENAMENAMENAME' //*/\n"
+      "(e) { K(() => e, function later() { return e }) } }\n";
+
+  // Setup: define `await` and `top` in global scope for classic script parse.
+  {
+    v8::HandleScope handle_scope(isolate());
+    Local<Script> setup =
+        Script::Compile(
+            context(), NewString("var await = 6; globalThis.top = globalThis;"))
+            .ToLocalChecked();
+    CHECK(!setup->Run(context()).IsEmpty());
+  }
+
+  // Step 1: Compile and run as a classic script, populating the Isolate
+  // compilation cache and retaining catch-block closures in globalThis.__keep.
+  // Also compile as a module with the same resource name and create a module
+  // code cache, then age and flush the classic script's top-level bytecode.
+  {
+    v8::HandleScope handle_scope(isolate());
+    ScriptOrigin classic_origin = default_origin.AsScriptOrigin();
+    Local<Script> script =
+        Script::Compile(context(), NewString(kPolyglotSource), &classic_origin)
+            .ToLocalChecked();
+    CHECK(!script->Run(context()).IsEmpty());
+
+    ScriptOrigin module_origin(NewString("http://example.com/test.js"), 0, 0,
+                               false, -1, Local<Value>(), false, false,
+                               /*is_module=*/true);
+    ScriptCompiler::Source module_source(NewString(kPolyglotSource),
+                                         module_origin);
+    Local<Module> module =
+        ScriptCompiler::CompileModule(isolate(), &module_source)
+            .ToLocalChecked();
+    cached_data.reset(
+        ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript()));
+
+    i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
+  }
+
+  // Step 2: Flush the classic script's top-level bytecode via major GC.
+  {
+    i::DisableConservativeStackScanningScopeForTesting no_css_scope(
+        i_isolate->heap());
+    InvokeMajorGC(i_isolate);
+    InvokeMajorGC(i_isolate);
+  }
+
+  // Step 3a: Start off-thread consumption of the MODULE code cache, then notify
+  // SourceTextAvailable with the CLASSIC ScriptOrigin after Run() completes.
+  {
+    auto task_cached_data = std::make_unique<ScriptCompiler::CachedData>(
+        cached_data->data, cached_data->length,
+        ScriptCompiler::CachedData::BufferNotOwned);
+    DeserializeThread deserialize_thread(
+        ScriptCompiler::StartConsumingCodeCache(
+            isolate(), std::make_unique<ScriptCompiler::CachedData>(
+                           cached_data->data, cached_data->length,
+                           ScriptCompiler::CachedData::BufferNotOwned)));
+    CHECK(deserialize_thread.Start());
+    deserialize_thread.Join();
+
+    std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task =
+        deserialize_thread.TakeTask();
+    task->SourceTextAvailable(isolate(), NewString(kPolyglotSource),
+                              default_origin.AsScriptOrigin());
+    CHECK(!task->ShouldMergeWithExistingScript());
+
+    v8::HandleScope handle_scope(isolate());
+    ScriptCompiler::Source source(NewString(kPolyglotSource),
+                                  default_origin.AsScriptOrigin(),
+                                  task_cached_data.release(), task.release());
+    Local<Script> script =
+        ScriptCompiler::Compile(context(), &source,
+                                ScriptCompiler::kConsumeCodeCache)
+            .ToLocalChecked();
+    CHECK(!script.IsEmpty());
+    CHECK(source.GetCachedData()->rejected);
+
+    // Age and flush the recompiled classic script's top-level bytecode again so
+    // Step 3b also sees a flushed Script in the Isolate compilation cache.
+    i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
+  }
+  {
+    i::DisableConservativeStackScanningScopeForTesting no_css_scope(
+        i_isolate->heap());
+    InvokeMajorGC(i_isolate);
+    InvokeMajorGC(i_isolate);
+  }
+
+  // Step 3b: Also test calling SourceTextAvailable on the main thread BEFORE
+  // Run() executes on the background thread.
+  {
+    auto task_cached_data = std::make_unique<ScriptCompiler::CachedData>(
+        cached_data->data, cached_data->length,
+        ScriptCompiler::CachedData::BufferNotOwned);
+    std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task(
+        ScriptCompiler::StartConsumingCodeCache(
+            isolate(), std::make_unique<ScriptCompiler::CachedData>(
+                           cached_data->data, cached_data->length,
+                           ScriptCompiler::CachedData::BufferNotOwned)));
+    task->SourceTextAvailable(isolate(), NewString(kPolyglotSource),
+                              default_origin.AsScriptOrigin());
+
+    DeserializeThread deserialize_thread(task.release());
+    CHECK(deserialize_thread.Start());
+    deserialize_thread.Join();
+    task = deserialize_thread.TakeTask();
+    CHECK(!task->ShouldMergeWithExistingScript());
+
+    v8::HandleScope handle_scope(isolate());
+    ScriptCompiler::Source source(NewString(kPolyglotSource),
+                                  default_origin.AsScriptOrigin(),
+                                  task_cached_data.release(), task.release());
+    Local<Script> script =
+        ScriptCompiler::Compile(context(), &source,
+                                ScriptCompiler::kConsumeCodeCache)
+            .ToLocalChecked();
+    CHECK(!script.IsEmpty());
+    CHECK(source.GetCachedData()->rejected);
+
+    Local<Script> run_kept =
+        Script::Compile(context(),
+                        NewString("for (const f of globalThis.__keep) f();"))
+            .ToLocalChecked();
+    CHECK(!run_kept->Run(context()).IsEmpty());
+  }
+}
+
 }  // namespace v8
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.