CVE-2026-3927
Overview
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
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/)