CVE-2026-3927
Overview
Background
- Picture-in-Picture (PiP) windows
- always-on-top browser windows for Document PiP (
PictureInPictureBrowserFrameView, whose title showslocation_bar_model_->GetURLForDisplay()) and Video PiP (VideoOverlayWindowViews, whoseoriginlabel shows the source site). - Bidirectional (bidi) text / RTLO
- Unicode layout rules that reorder right-to-left runs (and override characters such as Right-to-Left Override) when rendering, so the displayed order of an origin string can differ from its logical order.
- `gfx::DirectionalityMode::DIRECTIONALITY_AS_URL`
- a
views::Labeldirectionality mode, recommended by Chromium’s URL display guidelines, that always lays the text out left-to-right as a URL instead of inferring direction from the content.
Root Cause Analysis
Both PiP surfaces showed origin text in a views::Label created with the default directionality mode. The Document PiP title was built with views::Builder<views::Label>() followed by .SetText(location_bar_model_->GetURLForDisplay()), and the Video PiP origin label was constructed with only text, context and style. With the default mode, the label’s text direction follows its content. According to the commit message, RTL/RTLO characters in a malicious domain could therefore make the displayed origin look like a trusted domain, which violates the URL display guideline that origins must always render left-to-right.
The fix constructs both labels with gfx::DirectionalityMode::DIRECTIONALITY_AS_URL. The Document PiP title is now built with std::make_unique<views::Label>(GetURLForDisplay(), CONTEXT_LABEL, STYLE_PRIMARY, DIRECTIONALITY_AS_URL), and the Video PiP origin label gets the same fourth argument. The new tests check that the label direction is base::i18n::LEFT_TO_RIGHT both initially and after an RTL string (u"אבג") is set.
DIRECTIONALITY_AS_URL forces left-to-right URL layout whatever the content is.Attack Path
- Prepare a bidi-laden domain the attacker controls a domain that contains right-to-left characters, which the commit describes as RTLO characters in a malicious domain, chosen so that reordered display looks like a trusted site (inferred: the diff does not give a concrete hostname).
- Open a PiP window
a page on that origin opens Document PiP or Video PiP, so the browser shows
PictureInPictureBrowserFrameView’swindow_title_orVideoOverlayWindowViews’soriginlabel (inferred: the API entry points are not in the diff). - Misleading origin display before the patch, the labels used content-derived directionality, so the bidi algorithm reordered the domain and the PiP window appeared to belong to a different, trusted origin.
- Social engineering the user trusts content shown in the PiP window because of the spoofed origin label (inferred).
Impact Assessment
views UI (chrome/browser/ui/views). It does not involve memory corruption, and the advisory text supplied here does not report in-the-wild exploitation.Changed Functions
| Function | Change | Notes |
|---|---|---|
GetFadeAnimatorForTestingchrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc |
modified | |
FakeLinuxUiGetterchrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc |
modified | |
TEST_Fchrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc |
modified |
Files Changed
chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.ccchrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.hchrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.ccchrome/browser/ui/views/overlay/video_overlay_window_views.ccchrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
Audit Directions
- Origin labels without `DIRECTIONALITY_AS_URL`search
chrome/browser/ui/viewsforviews::Labels that displayGetURLForDisplay(), origins or hosts (permission bubbles, download, web-app, sharing and capture indicators) and are not created withgfx::DirectionalityMode::DIRECTIONALITY_AS_URL. - Builder-style label construction
views::Builder<views::Label>()creates labels with the default directionality, so audit builder chains thatSetTexta URL or origin and do not pass a pre-constructed label. - Secondary window chromecheck other non-tabbed browser windows (app windows, popups, overlays, Android/ChromeOS equivalents) that draw the source origin themselves, outside the hardened omnibox.
Patch
From 58a8c057b2b3b113e41ef8d8fb1228eb05d5a10c Mon Sep 17 00:00:00 2001
From: Benjamin Keen <bkeen@google.com>
Date: Tue, 20 Jan 2026 14:54:43 -0800
Subject: [PATCH] Set Pip window title directionality mode to DIRECTIONALITY_AS_URL
This change addresses a security UI spoofing vulnerability in both
Document and Video Picture-in-Picture (PiP) windows where Right-to-Left
Override (RTLO) characters in a malicious domain could cause the
displayed origin to appear as a trusted domain.
The fix ensures that the `views::Label` used for the PiP window title
always renders text in a Left-to-Right (LTR) direction. This is achieved
by setting the label directionality mode to `DIRECTIONALITY_AS_URL`, as
suggested in
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/url_display_guidelines/url_display_guidelines.md#rtl
Bug: 474948986
Change-Id: I56298ea3f267dfb5be8509addb30bbd0b34f1163
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7488911
Reviewed-by: Fr <beaufort.francois@gmail.com>
Commit-Queue: Benjamin Keen <bkeen@google.com>
Cr-Commit-Position: refs/heads/main@{#1571906}
---
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index cf08191..41457ef5 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -48,6 +48,7 @@
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/text_constants.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/animation/compositor_animation_runner.h"
@@ -595,9 +596,12 @@
// Creates the window title.
top_bar_container_view_->AddChildView(
- views::Builder<views::Label>()
+ views::Builder<views::Label>(
+ std::make_unique<views::Label>(
+ location_bar_model_->GetURLForDisplay(),
+ views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY,
+ gfx::DirectionalityMode::DIRECTIONALITY_AS_URL))
.CopyAddressTo(&window_title_)
- .SetText(location_bar_model_->GetURLForDisplay())
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetElideBehavior(elide_behavior)
.SetProperty(
@@ -1553,6 +1557,12 @@
return window_title_;
}
+void PictureInPictureBrowserFrameView::SetWindowTitleForTesting( // IN-TEST
+ const std::u16string& title) {
+ CHECK(window_title_);
+ window_title_->SetText(title);
+}
+
PictureInPictureWidgetFadeAnimator*
PictureInPictureBrowserFrameView::GetFadeAnimatorForTesting() {
return fade_animator_.get();
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index b307ae5..33aad98 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -184,6 +184,7 @@
views::View* GetBackToTabButtonForTesting();
views::View* GetCloseButtonForTesting();
views::Label* GetWindowTitleForTesting();
+ void SetWindowTitleForTesting(const std::u16string& title);
PictureInPictureWidgetFadeAnimator* GetFadeAnimatorForTesting();
// These values are persisted to logs. Entries should not be renumbered and
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
index 9b149cd..ac82d0d 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
@@ -4,6 +4,7 @@
#include <optional>
+#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
@@ -1085,6 +1086,26 @@
/*include_app_name=*/false));
}
+IN_PROC_BROWSER_TEST_F(PictureInPictureBrowserFrameViewTest,
+ WindowTitleHasCorrectDirectionality) {
+ ASSERT_NO_FATAL_FAILURE(SetUpDocumentPIP());
+ views::Label* window_title = pip_frame_view()->GetWindowTitleForTesting();
+ ASSERT_NE(nullptr, window_title);
+
+ // The directionality should be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+ window_title->GetTextDirectionForTesting());
+
+ // Set the window title to a RTL string.
+ const char16_t kRtl[] = u"אבג";
+ pip_frame_view()->SetWindowTitleForTesting(kRtl);
+ EXPECT_EQ(kRtl, window_title->GetText());
+
+ // The directionality should still be LTR.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+ window_title->GetTextDirectionForTesting());
+}
+
#if BUILDFLAG(IS_LINUX)
class FakeLinuxUiGetter : public ui::LinuxUiGetter {
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
index 8ccc5385..315bbdb 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
@@ -64,6 +64,7 @@
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/resize_utils.h"
#include "ui/gfx/geometry/skia_conversions.h"
+#include "ui/gfx/text_constants.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget_delegate.h"
@@ -1107,9 +1108,9 @@
auto favicon_view = std::make_unique<views::ImageView>();
favicon_view->SetSize(kFaviconSize);
- auto origin = std::make_unique<views::Label>(std::u16string(),
- views::style::CONTEXT_LABEL,
- views::style::STYLE_BODY_4);
+ auto origin = std::make_unique<views::Label>(
+ std::u16string(), views::style::CONTEXT_LABEL, views::style::STYLE_BODY_4,
+ gfx::DirectionalityMode::DIRECTIONALITY_AS_URL);
origin->SetEnabledColor(ui::kColorSysOnSurface);
origin->SetBackgroundColor(SK_ColorTRANSPARENT);
origin->SetHorizontalAlignment(gfx::ALIGN_LEFT);
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
index 6933ba2..0abc584 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
@@ -8,6 +8,7 @@
#include <memory>
#include <utility>
+#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
@@ -1062,6 +1063,22 @@
EXPECT_EQ(origin->GetText(), u"google.com");
}
+TEST_F(VideoOverlayWindowViewsTest, OriginLabelHasCorrectDirectionality) {
+ views::Label* origin = overlay_window().origin_for_testing();
+ ASSERT_NE(nullptr, origin);
+
+ // The directionality should be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+
+ // Set the source title to a RTL string.
+ const char16_t kRtl[] = u"אבג";
+ overlay_window().SetSourceTitle(kRtl);
+ EXPECT_EQ(kRtl, origin->GetText());
+
+ // The directionality should still be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+}
+
TEST_F(VideoOverlayWindowViewsTest,
ControlsNeverHideWhileProgressBarIsDragged) {
overlay_window().ShowInactive();
Regression Test / PoC
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
index 9b149cd..ac82d0d 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
@@ -4,6 +4,7 @@
#include <optional>
+#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
@@ -1085,6 +1086,26 @@
/*include_app_name=*/false));
}
+IN_PROC_BROWSER_TEST_F(PictureInPictureBrowserFrameViewTest,
+ WindowTitleHasCorrectDirectionality) {
+ ASSERT_NO_FATAL_FAILURE(SetUpDocumentPIP());
+ views::Label* window_title = pip_frame_view()->GetWindowTitleForTesting();
+ ASSERT_NE(nullptr, window_title);
+
+ // The directionality should be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+ window_title->GetTextDirectionForTesting());
+
+ // Set the window title to a RTL string.
+ const char16_t kRtl[] = u"אבג";
+ pip_frame_view()->SetWindowTitleForTesting(kRtl);
+ EXPECT_EQ(kRtl, window_title->GetText());
+
+ // The directionality should still be LTR.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+ window_title->GetTextDirectionForTesting());
+}
+
#if BUILDFLAG(IS_LINUX)
class FakeLinuxUiGetter : public ui::LinuxUiGetter {
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
index 6933ba2..0abc584 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
@@ -8,6 +8,7 @@
#include <memory>
#include <utility>
+#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
@@ -1062,6 +1063,22 @@
EXPECT_EQ(origin->GetText(), u"google.com");
}
+TEST_F(VideoOverlayWindowViewsTest, OriginLabelHasCorrectDirectionality) {
+ views::Label* origin = overlay_window().origin_for_testing();
+ ASSERT_NE(nullptr, origin);
+
+ // The directionality should be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+
+ // Set the source title to a RTL string.
+ const char16_t kRtl[] = u"אבג";
+ overlay_window().SetSourceTitle(kRtl);
+ EXPECT_EQ(kRtl, origin->GetText());
+
+ // The directionality should still be LTR to prevent spoofing.
+ EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+}
+
TEST_F(VideoOverlayWindowViewsTest,
ControlsNeverHideWhileProgressBarIsDragged) {
overlay_window().ShowInactive();
Original Bug Report
Chrome Windows - PIP Window Displays Incorrect Origin When Domain Uses RTL Characters
Report description
Chrome Windows - PIP Window Displays Incorrect Origin When Domain Uses RTL Characters
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
The problem
Please describe the technical details of the vulnerability
Summary
When a website uses Right-to-Left Override (RTLO) characters in its subdomain, the browser’s Picture-in-Picture (PiP) window displays an incorrect and misleading origin. Instead of showing the attacker-controlled root domain (eTLD+1), the PiP UI renders a spoofed trusted domain, allowing origin impersonation.
Steps to Reproduce
- Register or host a domain containing RTLO characters in the subdomain. POC URL:http://xn–mgb.accounts.login.apple.com.xn–mgb.https.google.com.summa.sbs/test.html
- Click the PIP button
- Observe the origin shown in the PiP window.
- Notice that the displayed origin shows a spoofed trusted domain (apple.com.I) instead of the real eTLD+1 (summa.sbs)
Expected Behavior The PiP window should always display the actual eTLD+1 of the media’s origin and sanitize or neutralize RTL characters to prevent misleading rendering.
Actual Behavior The PiP UI renders RTL characters in a way that causes the origin to appear as a different, trusted domain, hiding the attacker-controlled root domain.
Proof of Concept Displayed in PiP: apple.com.I
Actual media origin: summa.sbs
Impact analysis
An attacker can abuse RTL characters in a domain to make the Picture-in-Picture (PiP) window display a trusted brand name (for example, apple.com) while the actual content is served from an attacker-controlled domain (summa.sbs). Since PiP is a trusted, persistent UI element, users are likely to rely on the displayed origin for trust decisions. This enables convincing phishing, social-engineering, and malware delivery scenarios, as users may interact with or trust content believing it originates from a legitimate site. The absence of any alternative way to verify the true origin further increases the risk of exploitation.
Refer: https://issues.chromium.org/issues/40065117 https://issues.chromium.org/issues/40066780
The cause
What version of Chrome have you found the security issue in?
Version 143.0.7499.193 (Official Build) (64-bit)
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Security UI Spoofing
How would you like to be publicly acknowledged for your report?
Barath Stalin K (https://www.linkedin.com/in/barathstalin/)