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 <[email protected]> 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 <[email protected]> Commit-Queue: Benjamin Keen <[email protected]> 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/)