Chrome · Track › Media
CVE-2026-106200
UAF in Track (Media)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/html/track/cue_timeline.cc |
modified | |
forthird_party/blink/renderer/core/html/track/cue_timeline.cc |
modified | |
forthird_party/blink/renderer/core/html/track/text_track_container.cc |
modified |
Files Changed
third_party/blink/renderer/core/html/track/text_track_container.ccthird_party/blink/renderer/core/html/track/cue_timeline.hthird_party/blink/renderer/core/html/track/cue_timeline.cc
Patch
From 082a72b30467b77193ec1cd31614eec5b3df8b3f Mon Sep 17 00:00:00 2001 From: Dale Curtis <[email protected]> Date: Fri, 02 Oct 2026 02:04:19 -0700 Subject: [PATCH] [Media] Use HeapVector<Member<TextTrackCue>> for active cues CueTimeline::currently_active_cues_ previously used a WTF::Vector of CueInterval objects holding untraced raw TextTrackCue* pointers. When copied in TextTrackContainer::UpdateDisplay(), the backing store was allocated on PartitionAlloc rather than the Oilpan heap, meaning Oilpan could not trace pending cues if synchronous script removed a cue and triggered garbage collection mid-iteration. Replace CueTimeline::currently_active_cues_ (and the local cue lists in TimeMarchesOn) with HeapVector<Member<TextTrackCue>>, trace it in CueTimeline::Trace(), and return a HeapVector<Member<TextTrackCue>> snapshot by value from CurrentlyActiveCues(). Fixed: 567936270, 568422505 Change-Id: Ic3d4ee30b0885a7b91dac8d98eb5a37f4471da20 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8503663 Commit-Queue: Fredrik Söderquist <[email protected]> Auto-Submit: Dale Curtis <[email protected]> Reviewed-by: Fredrik Söderquist <[email protected]> Cr-Commit-Position: refs/heads/main@{#1709867} --- diff --git a/third_party/blink/renderer/core/html/track/cue_timeline.cc b/third_party/blink/renderer/core/html/track/cue_timeline.cc index 4c1a766..8137bd1 100644 --- a/third_party/blink/renderer/core/html/track/cue_timeline.cc +++ b/third_party/blink/renderer/core/html/track/cue_timeline.cc @@ -104,7 +104,7 @@ CueInterval interval = CreateCueInterval(cue); cue_tree_.Remove(interval); - wtf_size_t index = currently_active_cues_.Find(interval); + wtf_size_t index = currently_active_cues_.Find(cue); if (index != kNotFound) { DCHECK(cue->IsActive()); currently_active_cues_.EraseAt(index); @@ -193,15 +193,18 @@ // kHaveNothing. if (media_element.getReadyState() != HTMLMediaElement::kHaveNothing && media_element.GetWebMediaPlayer()) { - current_cues = cue_tree_.AllOverlaps(movie_time, movie_time); + Vector<CueInterval> current_intervals = + cue_tree_.AllOverlaps(movie_time, movie_time); + current_cues.reserve(current_intervals.size()); + for (const auto& interval : current_intervals) { + current_cues.push_back(interval.Data()); + } } - CueList previous_cues; - // 2 - Let other cues be a list of cues, initialized to contain all the cues // of hidden, showing, and showing by default text tracks of the media // element that are not present in current cues. - previous_cues = currently_active_cues_; + CueList previous_cues = currently_active_cues_; // 3 - Let last time be the current playback position at the time this // algorithm was last run for this media element, if this is not the first @@ -217,9 +220,9 @@ // Otherwise, let missed cues be an empty list. CueList missed_cues; if (last_time >= 0 && last_seek_time < movie_time) { - CueList potentially_skipped_cues = + Vector<CueInterval> potentially_skipped_cues = cue_tree_.AllOverlaps(last_time, movie_time); - missed_cues.ReserveInitialCapacity(potentially_skipped_cues.size()); + missed_cues.reserve(potentially_skipped_cues.size()); for (CueInterval cue : potentially_skipped_cues) { // Consider cues that may have been missed since the last seek time. Do @@ -229,7 +232,7 @@ if (cue.Low() > std::max(last_seek_time, last_time) && cue.High() < movie_time && !newly_introduced_cues_.Contains(cue.Data())) { - missed_cues.push_back(cue); + missed_cues.push_back(cue.Data()); } } } @@ -254,19 +257,21 @@ for (wtf_size_t i = 0; !active_set_changed && i < previous_cues_size; ++i) { if (!current_cues.Contains(previous_cues[i]) && - previous_cues[i].Data()->IsActive()) + previous_cues[i]->IsActive()) { active_set_changed = true; + } } - for (CueInterval current_cue : current_cues) { + for (const auto& current_cue : current_cues) { // Notify any cues that are already active of the current time to mark // past and future nodes. Any inactive cues have an empty display state; // they will be notified of the current time when the display state is // updated. - if (current_cue.Data()->IsActive()) - current_cue.Data()->UpdatePastAndFutureNodes(movie_time); - else + if (current_cue->IsActive()) { + current_cue->UpdatePastAndFutureNodes(movie_time); + } else { active_set_changed = true; + } } if (!active_set_changed) @@ -279,15 +284,16 @@ // cues, then immediately pause the media element. for (wtf_size_t i = 0; !media_element.paused() && i < previous_cues_size; ++i) { - if (previous_cues[i].Data()->pauseOnExit() && - previous_cues[i].Data()->IsActive() && - !current_cues.Contains(previous_cues[i])) + if (previous_cues[i]->pauseOnExit() && previous_cues[i]->IsActive() && + !current_cues.Contains(previous_cues[i])) { media_element.pause(); + } } for (wtf_size_t i = 0; !media_element.paused() && i < missed_cues_size; ++i) { - if (missed_cues[i].Data()->pauseOnExit()) + if (missed_cues[i]->pauseOnExit()) { media_element.pause(); + } } // 8 - Let events be a list of tasks, initially empty. Each task in this @@ -301,8 +307,7 @@ for (const auto& missed_cue : missed_cues) { // 9 - For each text track cue in missed cues, prepare an event named enter // for the TextTrackCue object with the text track cue start time. - event_tasks.push_back( - std::make_pair(missed_cue.Data()->startTime(), missed_cue.Data())); + event_tasks.push_back(std::make_pair(missed_cue->startTime(), missed_cue)); // 10 - For each text track [...] in missed cues, prepare an event // named exit for the TextTrackCue object with the with the later of @@ -313,9 +318,8 @@ // checked when these tasks are actually queued below. This doesn't // affect sorting events before dispatch either, because the exit // event has the same time as the enter event. - if (missed_cue.Data()->startTime() < missed_cue.Data()->endTime()) { - event_tasks.push_back( - std::make_pair(missed_cue.Data()->endTime(), missed_cue.Data())); + if (missed_cue->startTime() < missed_cue->endTime()) { + event_tasks.push_back(std::make_pair(missed_cue->endTime(), missed_cue)); } } @@ -325,7 +329,7 @@ // TextTrackCue object with the text track cue end time. if (!current_cues.Contains(previous_cue)) { event_tasks.push_back( - std::make_pair(previous_cue.Data()->endTime(), previous_cue.Data())); + std::make_pair(previous_cue->endTime(), previous_cue)); } } @@ -335,7 +339,7 @@ // TextTrackCue object with the text track cue start time. if (!previous_cues.Contains(current_cue)) { event_tasks.push_back( - std::make_pair(current_cue.Data()->startTime(), current_cue.Data())); + std::make_pair(current_cue->startTime(), current_cue)); } } @@ -392,13 +396,12 @@ // cues, and unset the text track cue active flag of all the cues in the // other cues. for (const auto& cue : current_cues) - cue.Data()->SetIsActive(true); + cue->SetIsActive(true); for (const auto& previous_cue : previous_cues) { if (!current_cues.Contains(previous_cue)) { - TextTrackCue* cue = previous_cue.Data(); - cue->SetIsActive(false); - cue->RemoveDisplayTree(); + previous_cue->SetIsActive(false); + previous_cue->RemoveDisplayTree(); } } @@ -410,12 +413,13 @@ void CueTimeline::UpdateActiveCuePastAndFutureNodes() { double const movie_time = MediaElement().currentTime(); - for (auto cue : currently_active_cues_) { - DCHECK(cue.Data()->IsActive()); - if (!cue.Data()->track() || !cue.Data()->track()->IsRendered()) + for (const auto& cue : currently_active_cues_) { + DCHECK(cue->IsActive()); + if (!cue->track() || !cue->track()->IsRendered()) { continue; + } - cue.Data()->UpdatePastAndFutureNodes(movie_time); + cue->UpdatePastAndFutureNodes(movie_time); } SetCueTimestampEventTimer(); @@ -466,8 +470,8 @@ // Deactivate all active cues // "The user agent must synchronously unset this flag ... whenever the media // element's readyState is changed back to HAVE_NOTHING." - for (auto cue : currently_active_cues_) { - cue.Data()->SetIsActive(false); + for (const auto& cue : currently_active_cues_) { + cue->SetIsActive(false); } currently_active_cues_.clear(); @@ -521,8 +525,8 @@ double const movie_time = media_element.currentTime(); double next_cue_timestamp_event = kInfinity; - for (auto cue : currently_active_cues_) { - auto const timestamp = cue.Data()->GetNextIntraCueTime(movie_time); + for (const auto& cue : currently_active_cues_) { + auto const timestamp = cue->GetNextIntraCueTime(movie_time); next_cue_timestamp_event = std::min(next_cue_timestamp_event, timestamp.value_or(kInfinity)); } @@ -552,6 +556,7 @@ void CueTimeline::Trace(Visitor* visitor) const { visitor->Trace(media_element_); + visitor->Trace(currently_active_cues_); visitor->Trace(newly_introduced_cues_); visitor->Trace(cue_event_timer_); visitor->Trace(cue_timestamp_event_timer_); diff --git a/third_party/blink/renderer/core/html/track/cue_timeline.h b/third_party/blink/renderer/core/html/track/cue_timeline.h index b9e1c815..91bd29e 100644 --- a/third_party/blink/renderer/core/html/track/cue_timeline.h +++ b/third_party/blink/renderer/core/html/track/cue_timeline.h @@ -12,6 +12,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/html/track/text_track_cue.h" #include "third_party/blink/renderer/core/html/track/vtt/vtt_cue.h" +#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/timer.h" #include "third_party/blink/renderer/platform/wtf/pod_interval_tree.h" @@ -30,7 +31,7 @@ // become stale pointers. using CueIntervalTree = PodIntervalTree<double, TextTrackCue*>; typedef CueIntervalTree::IntervalType CueInterval; -typedef Vector<CueInterval> CueList; +using CueList = HeapVector<Member<TextTrackCue>>; // This class manages the timeline and rendering updates of cues associated // with TextTracks. Owned by a HTMLMediaElement. diff --git a/third_party/blink/renderer/core/html/track/text_track_container.cc b/third_party/blink/renderer/core/html/track/text_track_container.cc index f86b11b..b80ad7b6 100644 --- a/third_party/blink/renderer/core/html/track/text_track_container.cc +++ b/third_party/blink/renderer/core/html/track/text_track_container.cc @@ -211,11 +211,10 @@ // corresponding CSS boxes added to output, in text track cue order, run the // following substeps: double movie_time = video.currentTime(); - for (const auto& active_cue : active_cues) { - TextTrackCue* cue = active_cue.Data(); - + for (const auto& cue : active_cues) { // Cues may have been deactivated or removed if synchronous script ran - // during a previous iteration (e.g., via video.load()). + // during a previous iteration (e.g., via video.load() or + // track.removeCue()). if (!cue->IsActive() || !cue->track() || !cue->track()->IsRendered()) { continue; }
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page