Skip to content

Commit 80e82a8

Browse files
vmpstrmibrunin
authored andcommitted
[Backport] CVE-2024-11116: Inappropriate implementation in Paint
Manual backport of patch originally reviewed on https://chromium-review.googlesource.com/c/chromium/src/+/5894513: Reapply "[PH] Disable cross origin paint holding if there was no user activation." This reverts commit 011f2568aab9a77614d10ad913a18a825ea7d6bb. Original patch description: This patch disables paint holding if this is a cross origin navigation and there was no user activation. This is a safety measure to prevent sites from continually displaying mismatched URL and content. With regular user behavior (clicks, etc), the behavior should be unchanged since this counts as user activation. Difference from original CL: The difference is that we post a task to timeout paintholding, which allows embedding to differ and happen further down in the stack. Details: The surface eviction happens in this stack content::DelegatedFrameHost::ResetFallbackToFirstNavigationSurface() content::RenderWidgetHostImpl::ClearDisplayedGraphics() content::RenderWidgetHostImpl::ForceFirstFrameAfterNavigationTimeout() content::RenderFrameHostManager::CommitPendingIfNecessary() content::RenderFrameHostManager::DidNavigateFrame() content::Navigator::DidNavigate() There is a bifurcation in ResetFallbackToFirstNavigationSurface to decide whether to evict the delegated frame. This decision is based on whether we have an first local surface id after navigation. On non-Mac system, this local surface id is set in a stack similar to the one below: content::DelegatedFrameHost::EmbedSurface() content::RenderWidgetHostViewAura::SynchronizeVisualProperties() content::RenderWidgetHostViewAura::ShowWithVisibility() content::RenderFrameHostManager::CommitPendingIfNecessary() content::RenderFrameHostManager::DidNavigateFrame() content::Navigator::DidNavigate() Importantly, this happens _before_ we reset fallback, so in typical cases we avoid eviction of the frame and simply reset its surface. On Mac, the stack that sets the frame is below: content::DelegatedFrameHost::EmbedSurface() content::BrowserCompositorMac::DidNavigate() content::RenderWidgetHostImpl::DidNavigate() content::RenderFrameHostImpl::DidCommitNavigation() ... content::mojom::NavigationClient_CommitNavigation_ForwardToCallback This call happens _after_ we reset the fallback, so in typical cases we evict the frame before embedding a new one. This is a cause for a lot of test failures (and ultimately the reason for the revert). Because the reset fallback path never happened synchronously with DidNavigate, it isn't clear at this time whether this poses a problem in non-test cases. Out of abundance of caution, I propose posting a (non-delayed) task to remove paint holding. In practice this means potentially having paintholding in place while the UI thread is busy. This, however, is still a mitigation for the initial bug, albeit one that does not have strict guarantees. [email protected] Bug: 40942531 Change-Id: Id45d1e2267147da2a6f4351cb95d3d8002d8f7ae Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5894513 Reviewed-by: Charlie Reis <[email protected]> Commit-Queue: Vladimir Levin <[email protected]> Reviewed-by: Nate Fischer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1363640} Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/604272 Reviewed-by: Michal Klocek <[email protected]> Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/607613 Reviewed-by: Anu Aliyas <[email protected]>
1 parent def42ea commit 80e82a8

File tree

8 files changed

+93
-29
lines changed

8 files changed

+93
-29
lines changed

chromium/content/browser/renderer_host/delegated_frame_host.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,10 @@ void DelegatedFrameHost::ContinueDelegatedFrameEviction(
503503
// preventing the FrameTree from being traversed. This could happen during
504504
// navigation involving BFCache. This should not occur with
505505
// features::kEvictSubtree.
506-
DCHECK(!surface_ids.empty() ||
506+
// We do allow the surface ids to be empty if we
507+
// don't have a local surface id, since that means we don't have memory
508+
// allocated in viz.
509+
DCHECK(!surface_ids.empty() || !local_surface_id_.is_valid() ||
507510
!base::FeatureList::IsEnabled(features::kEvictSubtree));
508511
if (!surface_ids.empty()) {
509512
DCHECK(host_frame_sink_manager_);

chromium/content/browser/renderer_host/navigator.cc

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
3333
#include "content/browser/webui/web_ui_controller_factory_registry.h"
3434
#include "content/browser/webui/web_ui_impl.h"
35+
#include "content/common/features.h"
3536
#include "content/common/navigation_params_utils.h"
3637
#include "content/public/browser/browser_context.h"
3738
#include "content/public/browser/content_browser_client.h"
@@ -516,7 +517,38 @@ void Navigator::DidNavigate(
516517
// Store this information before DidNavigateFrame() potentially swaps RFHs.
517518
url::Origin old_frame_origin = old_frame_host->GetLastCommittedOrigin();
518519

519-
// Only allow paint holding for same-origin navigations.
520+
// RenderFrameHostImpl::DidNavigate will update the url, and may cause the
521+
// node to consider itself no longer on the initial empty document. Record
522+
// whether we're leaving the initial empty document before that.
523+
bool was_on_initial_empty_document =
524+
frame_tree_node->is_on_initial_empty_document();
525+
526+
// Allow main frame paint holding in the following cases:
527+
// - We don't have an animated transition. See crbug.com/360844863.
528+
// - At least one of the following conditions is true:
529+
// - This is a navigation from the initial document. This part helps with
530+
// tests. See crbug.com/367623929.
531+
// - This is a same origin navigation (or we're not limiting cross-origin
532+
// paint holding)
533+
// - There is a user activation. This means that the user interacted with
534+
// the page. Commonly used attacks are done without user activation --
535+
// which will not enable paint holding. However, if the user interacts
536+
// with the page, we treat it as a valid case for paint holding.
537+
// - The client allows non-activated cross origin paintholding, which is
538+
// currently the case with webview.
539+
//
540+
// See https://issues.chromium.org/40942531 for reasons we limit paint
541+
// holding.
542+
ContentBrowserClient* client = GetContentClient()->browser();
543+
const bool allow_main_frame_paint_holding =
544+
(was_on_initial_empty_document ||
545+
old_frame_origin.IsSameOriginWith(params.origin) ||
546+
old_frame_host->HasStickyUserActivation() ||
547+
client->AllowNonActivatedCrossOriginPaintHolding() ||
548+
!base::FeatureList::IsEnabled(
549+
kLimitCrossOriginNonActivatedPaintHolding));
550+
551+
// Only allow subframe paint holding for same origin.
520552
const bool allow_subframe_paint_holding =
521553
old_frame_origin.IsSameOriginWith(params.origin);
522554

@@ -525,13 +557,16 @@ void Navigator::DidNavigate(
525557
// the frame we're navigating from, which might trigger those subframes to
526558
// run unload handlers. Those unload handlers should still see the old
527559
// frame's origin. See https://crbug.com/825283.
560+
const bool allow_paint_holding = frame_tree_node->IsMainFrame()
561+
? allow_main_frame_paint_holding
562+
: allow_subframe_paint_holding;
563+
528564
frame_tree_node->render_manager()->DidNavigateFrame(
529565
render_frame_host, navigation_request->common_params().has_user_gesture,
530566
was_within_same_document,
531567
navigation_request->browsing_context_group_swap()
532568
.ShouldClearProxiesOnCommit(),
533-
navigation_request->commit_params().frame_policy,
534-
allow_subframe_paint_holding);
569+
navigation_request->commit_params().frame_policy, allow_paint_holding);
535570

536571
// The main frame, same site, and cross-site navigation checks for user
537572
// activation mirror the checks in DocumentLoader::CommitNavigation() (note:
@@ -598,12 +633,6 @@ void Navigator::DidNavigate(
598633
render_frame_host->GetPage().SetContentsMimeType(params.contents_mime_type);
599634
}
600635

601-
// RenderFrameHostImpl::DidNavigate will update the url, and may cause the
602-
// node to consider itself no longer on the initial empty document. Record
603-
// whether we're leaving the initial empty document before that.
604-
bool was_on_initial_empty_document =
605-
frame_tree_node->is_on_initial_empty_document();
606-
607636
render_frame_host->DidNavigate(params, navigation_request.get(),
608637
was_within_same_document);
609638

chromium/content/browser/renderer_host/render_frame_host_manager.cc

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -732,10 +732,10 @@ void RenderFrameHostManager::DidNavigateFrame(
732732
bool is_same_document_navigation,
733733
bool clear_proxies_on_commit,
734734
const blink::FramePolicy& frame_policy,
735-
bool allow_subframe_paint_holding) {
735+
bool allow_paint_holding) {
736736
CommitPendingIfNecessary(render_frame_host, was_caused_by_user_gesture,
737737
is_same_document_navigation, clear_proxies_on_commit,
738-
allow_subframe_paint_holding);
738+
allow_paint_holding);
739739

740740
// Make sure any dynamic changes to this frame's sandbox flags and permissions
741741
// policy that were made prior to navigation take effect. This should only
@@ -772,7 +772,7 @@ void RenderFrameHostManager::CommitPendingIfNecessary(
772772
bool was_caused_by_user_gesture,
773773
bool is_same_document_navigation,
774774
bool clear_proxies_on_commit,
775-
bool allow_subframe_paint_holding) {
775+
bool allow_paint_holding) {
776776
if (!speculative_render_frame_host_) {
777777
// There's no speculative RenderFrameHost so it must be that the current
778778
// RenderFrameHost completed a navigation.
@@ -787,7 +787,7 @@ void RenderFrameHostManager::CommitPendingIfNecessary(
787787
// A cross-RenderFrameHost navigation completed, so show the new renderer.
788788
CommitPending(std::move(speculative_render_frame_host_),
789789
std::move(stored_page_to_restore_), clear_proxies_on_commit,
790-
allow_subframe_paint_holding);
790+
allow_paint_holding);
791791

792792
if (GetNavigationQueueingFeatureLevel() >=
793793
NavigationQueueingFeatureLevel::kAvoidRedundantCancellations) {
@@ -844,9 +844,26 @@ void RenderFrameHostManager::CommitPendingIfNecessary(
844844
// output on prerender activation.
845845
if (render_frame_host_->lifecycle_state() !=
846846
LifecycleStateImpl::kPrerendering) {
847-
static_cast<RenderWidgetHostImpl*>(
848-
render_frame_host_->GetView()->GetRenderWidgetHost())
849-
->StartNewContentRenderingTimeout();
847+
auto* rwhi = static_cast<RenderWidgetHostImpl*>(
848+
render_frame_host_->GetView()->GetRenderWidgetHost());
849+
850+
rwhi->StartNewContentRenderingTimeout();
851+
// Force the timer to expire immediately if we don't allow main frame
852+
// paint holding.
853+
if (frame_tree_node_->IsMainFrame() && !allow_paint_holding) {
854+
// We post task here, since this evicts a surface but the embedding of a
855+
// new surface would be done in the same stack as this call. The
856+
// ordering of whether the new surface has or has not yet been embedded
857+
// differs for different platforms, and we always want the new surface
858+
// to be embedded before we evict. Hence, we post a task. In practice
859+
// this still disables paint holding unless this task is delayed for a
860+
// long time.
861+
GetUIThreadTaskRunner({})->PostTask(
862+
FROM_HERE,
863+
base::BindOnce(
864+
&RenderWidgetHostImpl::ForceFirstFrameAfterNavigationTimeout,
865+
rwhi->GetWeakPtr()));
866+
}
850867
}
851868
}
852869

@@ -1471,7 +1488,7 @@ void RenderFrameHostManager::PerformEarlyRenderFrameHostSwapIfNeeded(
14711488
CommitPending(
14721489
std::move(speculative_render_frame_host_), nullptr,
14731490
request->browsing_context_group_swap().ShouldClearProxiesOnCommit(),
1474-
/* allow_subframe_paint_holding */ false);
1491+
/* allow_paint_holding */ false);
14751492
request->SetAssociatedRFHType(
14761493
NavigationRequest::AssociatedRenderFrameHostType::CURRENT);
14771494

@@ -4346,7 +4363,7 @@ void RenderFrameHostManager::CommitPending(
43464363
std::unique_ptr<RenderFrameHostImpl> pending_rfh,
43474364
std::unique_ptr<StoredPage> pending_stored_page,
43484365
bool clear_proxies_on_commit,
4349-
bool allow_subframe_paint_holding) {
4366+
bool allow_paint_holding) {
43504367
TRACE_EVENT1("navigation", "RenderFrameHostManager::CommitPending",
43514368
"FrameTreeNode id", frame_tree_node_->frame_tree_node_id());
43524369
CHECK(pending_rfh);
@@ -4599,9 +4616,10 @@ void RenderFrameHostManager::CommitPending(
45994616
// valid surface id, because it already has that surface embedded through
46004617
// `RenderFrameHostImpl::WillLeaveBackForwardCache` and the timeout that
46014618
// would be set here will clear that frame (incorrectly).
4602-
if (is_main_frame && old_view && old_view != new_view) {
4603-
// We should take the fallback if we're not coming from BFCache or if we
4604-
// don't have a valid surface id to display.
4619+
if (is_main_frame && allow_paint_holding && old_view && old_view != new_view) {
4620+
// If allowed, we should take the fallback in any of the following cases:
4621+
// - We're not coming from BFCache
4622+
// - We don't have a valid surface id to display.
46054623
auto* render_widget_host_view_base =
46064624
static_cast<RenderWidgetHostViewBase*>(render_frame_host_->GetView());
46074625
should_take_fallback_content =
@@ -4736,7 +4754,7 @@ void RenderFrameHostManager::CommitPending(
47364754
if (proxy_to_parent_or_outer_delegate) {
47374755
proxy_to_parent_or_outer_delegate->SetChildRWHView(
47384756
static_cast<RenderWidgetHostViewChildFrame*>(new_view),
4739-
old_size ? &*old_size : nullptr, allow_subframe_paint_holding);
4757+
old_size ? &*old_size : nullptr, allow_paint_holding);
47404758
}
47414759

47424760
if (render_frame_host_->is_local_root()) {
@@ -5145,7 +5163,7 @@ void RenderFrameHostManager::CreateNewFrameForInnerDelegateAttachIfNecessary() {
51455163

51465164
CommitPending(std::move(speculative_render_frame_host_), nullptr,
51475165
false /* clear_proxies_on_commit */,
5148-
/* allow_subframe_paint_holding */ false);
5166+
/* allow_paint_holding */ false);
51495167
NotifyPrepareForInnerDelegateAttachComplete(true /* success */);
51505168
}
51515169

chromium/content/browser/renderer_host/render_frame_host_manager.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ class CONTENT_EXPORT RenderFrameHostManager {
323323
bool is_same_document_navigation,
324324
bool clear_proxies_on_commit,
325325
const blink::FramePolicy& frame_policy,
326-
bool allow_subframe_paint_holding);
326+
bool allow_paint_holding);
327327

328328
// Called when this frame's opener is changed to the frame specified by
329329
// |opener_frame_token| in |source_site_instance_group|'s process. This
@@ -972,19 +972,18 @@ class CONTENT_EXPORT RenderFrameHostManager {
972972
// |clear_proxies_on_commit| Indicates if the proxies and opener must be
973973
// removed during the commit. This can happen following some BrowsingInstance
974974
// swaps, such as those for COOP.
975-
// |allow_subframe_paint_holding| Indicates that paint holding is allowed if
976-
// this is a subframe navigation.
975+
// |allow_paint_holding| Indicates whether paint holding is allowed.
977976
void CommitPending(std::unique_ptr<RenderFrameHostImpl> pending_rfh,
978977
std::unique_ptr<StoredPage> pending_stored_page,
979978
bool clear_proxies_on_commit,
980-
bool allow_subframe_paint_holding);
979+
bool allow_paint_holding);
981980

982981
// Helper to call CommitPending() in all necessary cases.
983982
void CommitPendingIfNecessary(RenderFrameHostImpl* render_frame_host,
984983
bool was_caused_by_user_gesture,
985984
bool is_same_document_navigation,
986985
bool clear_proxies_on_commit,
987-
bool allow_subframe_paint_holding);
986+
bool allow_paint_holding);
988987

989988
// Runs the unload handler in the old RenderFrameHost, after the new
990989
// RenderFrameHost has committed. |old_render_frame_host| will either be

chromium/content/common/features.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ BASE_FEATURE(kWindowOpenFileSelectFix,
5959
"WindowOpenFileSelectFix",
6060
base::FEATURE_ENABLED_BY_DEFAULT);
6161

62+
// Flag guard for fix for crbug.com/40942531.
63+
BASE_FEATURE(kLimitCrossOriginNonActivatedPaintHolding,
64+
"LimitCrossOriginNonActivatedPaintHolding",
65+
base::FEATURE_ENABLED_BY_DEFAULT);
66+
6267
// Please keep features in alphabetical order.
6368

6469
} // namespace content

chromium/content/common/features.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kSpeculativeServiceWorkerStartup);
7272
// Flag guard for fix for crbug.com/1414936.
7373
CONTENT_EXPORT BASE_DECLARE_FEATURE(kWindowOpenFileSelectFix);
7474

75+
CONTENT_EXPORT BASE_DECLARE_FEATURE(kLimitCrossOriginNonActivatedPaintHolding);
76+
7577
// Please keep features in alphabetical order.
7678

7779
} // namespace content

chromium/content/public/browser/content_browser_client.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,4 +1593,8 @@ bool ContentBrowserClient::
15931593
return true;
15941594
}
15951595

1596+
bool ContentBrowserClient::AllowNonActivatedCrossOriginPaintHolding() {
1597+
return false;
1598+
}
1599+
15961600
} // namespace content

chromium/content/public/browser/content_browser_client.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,10 @@ class CONTENT_EXPORT ContentBrowserClient {
26192619
// "Cache-control: no-store" header in BFCache.
26202620
virtual bool ShouldAllowBackForwardCacheForCacheControlNoStorePage(
26212621
content::BrowserContext* browser_context);
2622+
2623+
// Indicates whether this client allows paint holding in cross-origin
2624+
// navigations even if there was no user activation.
2625+
virtual bool AllowNonActivatedCrossOriginPaintHolding();
26222626
};
26232627

26242628
} // namespace content

0 commit comments

Comments
 (0)