CVE-2026-79054
Overview
Background
- `MediaPipelineImpl`
- the Chromecast CMA pipeline coordinator that owns the audio and video sub-pipelines and drives the shared media
backendon behalf of a cast session. - `pending_flush_task_`
- a member flag/task that stays set while a flush is outstanding; per the added comment, the backend is only stopped once every per-stream flush has completed, so a set flag means one or more streams are still flushing.
- Per-stream flush
- audio and video streams flush independently (
AvPipelineImpl::Flush, referenced in the comment), so one stream’s flush can finish while the other’s remains outstanding, leavingpending_flush_task_set. - `StartPlayingFrom`
- the entry point (per the commit subject and the test’s
media_pipeline.StartPlayingFrom(...)calls) that (re)starts playback from a timestamp, lazily initializing and starting thebackendand its AV pipelines.
Root Cause Analysis
The changed function in chromecast/media/cma/pipeline/media_pipeline_impl.cc (the enclosing name is StartPlayingFrom (inferred), since the @@ -209,7 +209,17 @@ header does not name it) previously only asserted the invariant that no flush was pending via DCHECK(!pending_flush_task_);. Because a DCHECK is compiled out of release builds, a StartPlayingFrom arriving while pending_flush_task_ was still set proceeded to restart the pipeline in production. As the added comment states, the backend is only stopped once every per-stream flush has completed; restarting an AV pipeline before the backend was stopped “would release that buffer while it is still in use,” i.e. free a buffer the backend still references — the use-after-free.
The fix replaces the removed DCHECK with a runtime guard: when pending_flush_task_ is set it logs an error, calls OnError(::media::PIPELINE_ERROR_ABORT), and returns early, so the pipeline is never restarted while a flush is outstanding and the still-in-use buffer is never released. The regression test MediaPipelineImplTest.StartPlayingFromIgnoredWhileFlushPending reproduces the asymmetric case (audio flush completes synchronously, video flush deferred) and asserts the backend is not restarted (EXPECT_CALL(*backend, Stop).Times(0), EXPECT_CALL(audio_decoder, PushBuffer(_)).Times(0)).
StartPlayingFrom while a flush is pending” was enforced only by a DCHECK, which is a no-op in release builds; the fix promotes it to an always-on runtime check that aborts the request via OnError(::media::PIPELINE_ERROR_ABORT) before any pipeline restart can release a buffer still in use by the backend.Attack Path
- Start playback and push a buffer
A media stream is loaded so
StartPlayingFromstarts thebackendand AV pipelines, and at least one previously pushed buffer is in use by the backend. - Trigger an asymmetric flush
A flush is initiated across both streams; one per-stream flush (e.g. audio) completes while the other (e.g. video) stays outstanding, so
pending_flush_task_remains set and the backend has not yet been stopped. - Reissue `StartPlayingFrom` during the pending flush
A second
StartPlayingFromarrives in this window; pre-patch, the release-buildDCHECKdid not block it, so the pipeline restarted while the backend still referenced the earlier buffer. - Use-after-free
Restarting the AV pipeline before the backend was stopped releases the still-in-use buffer, leaving the backend with a dangling reference (the freed buffer is subsequently accessed by the backend).
(inferred)that a specific dereference site or corruptible object exists beyond the buffer release the comment names. - Downstream escalation
Controlled reuse of the freed buffer to achieve memory corruption or code execution.
(inferred)— the diff proves only the release-while-in-use condition, not any specific read/write primitive or escape.
Impact Assessment
StartPlayingFrom while a per-stream flush is still pending, per the advisory (Bug class UAF, component Chromecast). The precondition is racing a StartPlayingFrom into the window where one stream’s flush has completed but another’s has not, so pending_flush_task_ is still set. The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The advisory states only “Use after free in Chromecast” and does not report in-the-wild exploitation or a severity rating in the provided metadata.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchromecast/media/cma/pipeline/media_pipeline_impl.cc |
modified | |
StallingFrameProviderchromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc |
modified | |
ifchromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc |
modified | |
TESTchromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc |
modified | |
BindOncechromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc |
modified |
Files Changed
chromecast/media/cma/pipeline/media_pipeline_impl_unittest.ccchromecast/media/cma/pipeline/media_pipeline_impl.cc
Audit Directions
- `DCHECK`-only invariants on externally reachable stateSearch for
DCHECK/CHECKguarding preconditions that can be violated by attacker-influenced ordering (flush, seek, restart, teardown); aDCHECKvanishes in release builds and must be a runtime check plus safe early return if the invariant is security-relevant. - Restart/start paths that assume completed teardownAudit other
MediaPipelineImplandAvPipelineImplentry points (Start/Stop/Flush/Initialize) for restarting or freeing resources while asynchronous, per-stream operations are still outstanding, especially where audio and video complete independently. - Buffer lifetime across backend boundariesReview places where a buffer pushed to the CMA
backend(PushBuffer) may be released or reallocated before the backend is confirmed stopped, since ownership straddles the pipeline and backend across asynchronous flush completion.
Patch
From 5d15272dcd4391a7ac06f596f88518bbb5393865 Mon Sep 17 00:00:00 2001
From: Richard Nichols <rknichols@google.com>
Date: Tue, 07 Jul 2026 10:24:47 -0700
Subject: [PATCH] [Chromecast] Reject StartPlayingFrom while a flush is pending
MediaPipelineImpl only stops the backend once every per-stream flush has
completed. A StartPlayingFrom that arrives while one stream's flush is
still outstanding can therefore restart an AV pipeline whose previously
pushed buffer is still in use by the backend.
that reports PIPELINE_ERROR_ABORT and returns early, and add a
regression test that drives an asymmetric audio/video flush.
Bug: 516764384
Change-Id: I37dd5e1a230c675c7e624436e183114695f18148
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8012487
Reviewed-by: Shawn Quereshi <shawnq@google.com>
Reviewed-by: Sandeep Vijayasekar <sandv@google.com>
Commit-Queue: Richard Nichols <rknichols@google.com>
Cr-Commit-Position: refs/heads/main@{#1658062}
---
diff --git a/chromecast/media/cma/pipeline/media_pipeline_impl.cc b/chromecast/media/cma/pipeline/media_pipeline_impl.cc
index 999afb6..3e86da8 100644
--- a/chromecast/media/cma/pipeline/media_pipeline_impl.cc
+++ b/chromecast/media/cma/pipeline/media_pipeline_impl.cc
@@ -209,7 +209,17 @@
LOG(INFO) << __FUNCTION__ << " t0=" << time.InMilliseconds();
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(audio_pipeline_ || video_pipeline_);
- DCHECK(!pending_flush_task_);
+
+ // The backend is only stopped once every per-stream flush has completed,
+ // so a flush that is still pending means the backend may still be using a
+ // previously pushed buffer (see AvPipelineImpl::Flush). Restarting any AV
+ // pipeline before the backend has been stopped would release that buffer
+ // while it is still in use.
+ if (pending_flush_task_) {
+ LOG(ERROR) << __FUNCTION__ << " called while a flush is still pending";
+ OnError(::media::PIPELINE_ERROR_ABORT);
+ return;
+ }
// Lazy initialize.
if (backend_state_ == BACKEND_STATE_UNINITIALIZED) {
diff --git a/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc b/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
index 88acfe8..bdc5ae0 100644
--- a/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
+++ b/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
@@ -10,10 +10,15 @@
#include "base/functional/callback_helpers.h"
#include "base/test/task_environment.h"
#include "chromecast/media/api/test/mock_cma_backend.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "chromecast/media/cma/pipeline/av_pipeline_client.h"
#include "chromecast/media/cma/pipeline/load_type.h"
#include "chromecast/media/cma/pipeline/video_pipeline_client.h"
#include "chromecast/media/cma/test/mock_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/channel_layout.h"
#include "media/base/encryption_scheme.h"
+#include "media/base/media_util.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/base/video_decoder_config.h"
@@ -26,9 +31,40 @@
namespace media {
namespace {
+using ::testing::_;
using ::testing::AtLeast;
+using ::testing::NiceMock;
using ::testing::Return;
+// A frame provider that never delivers any frames and whose Flush completion
+// can optionally be deferred indefinitely.
+class StallingFrameProvider : public CodedFrameProvider {
+ public:
+ explicit StallingFrameProvider(bool defer_flush)
+ : defer_flush_(defer_flush) {}
+
+ StallingFrameProvider(const StallingFrameProvider&) = delete;
+ StallingFrameProvider& operator=(const StallingFrameProvider&) = delete;
+
+ ~StallingFrameProvider() override = default;
+
+ void Read(ReadCB read_cb) override { pending_read_cb_ = std::move(read_cb); }
+
+ void Flush(base::OnceClosure flush_cb) override {
+ pending_read_cb_.Reset();
+ if (defer_flush_) {
+ pending_flush_cb_ = std::move(flush_cb);
+ } else {
+ std::move(flush_cb).Run();
+ }
+ }
+
+ private:
+ const bool defer_flush_;
+ ReadCB pending_read_cb_;
+ base::OnceClosure pending_flush_cb_;
+};
+
TEST(MediaPipelineImplTest, DoesNotCrashOnFlushWhenBufferingIsDisabled) {
base::test::TaskEnvironment task_environment;
@@ -60,6 +96,70 @@
media_pipeline.Flush(base::DoNothing());
}
+// When both an audio and a video stream are present, the per-stream flushes
+// can complete independently. StartPlayingFrom must not restart the backend
+// or any AV pipeline until both have completed and the backend has been
+// stopped.
+TEST(MediaPipelineImplTest, StartPlayingFromIgnoredWhileFlushPending) {
+ base::test::TaskEnvironment task_environment;
+
+ NiceMock<MockCmaBackend::AudioDecoder> audio_decoder;
+ NiceMock<MockCmaBackend::VideoDecoder> video_decoder;
+ ON_CALL(audio_decoder, SetConfig).WillByDefault(Return(true));
+ ON_CALL(video_decoder, SetConfig).WillByDefault(Return(true));
+
+ auto backend = std::make_unique<NiceMock<MockCmaBackend>>();
+ ON_CALL(*backend, Initialize).WillByDefault(Return(true));
+ ON_CALL(*backend, Start).WillByDefault(Return(true));
+ EXPECT_CALL(*backend, CreateAudioDecoder).WillOnce(Return(&audio_decoder));
+ EXPECT_CALL(*backend, CreateVideoDecoder).WillOnce(Return(&video_decoder));
+ EXPECT_CALL(*backend, Start).Times(1);
+ EXPECT_CALL(*backend, Stop).Times(0);
+
+ EXPECT_CALL(audio_decoder, PushBuffer(_)).Times(0);
+
+ MediaPipelineImpl media_pipeline;
+ media_pipeline.Initialize(LoadType::kLoadTypeMediaStream, std::move(backend),
+ /*is_buffering_enabled=*/false);
+
+ ::media::AudioDecoderConfig audio_config(
+ ::media::AudioCodec::kMP3, ::media::kSampleFormatS16,
+ ::media::ChannelLayoutConfig::Stereo(), 44100, ::media::EmptyExtraData(),
+ ::media::EncryptionScheme::kUnencrypted);
+ ASSERT_EQ(::media::PIPELINE_OK,
+ media_pipeline.InitializeAudio(
+ audio_config, AvPipelineClient(),
+ std::make_unique<StallingFrameProvider>(
+ /*defer_flush=*/false)));
+ ASSERT_EQ(
+ ::media::PIPELINE_OK,
+ media_pipeline.InitializeVideo(
+ {::media::VideoDecoderConfig(
+ ::media::VideoCodec::kH264, ::media::H264PROFILE_MAIN,
+ ::media::VideoDecoderConfig::AlphaMode::kIsOpaque,
+ ::media::VideoColorSpace(), ::media::kNoTransformation,
+ gfx::Size(640, 480), gfx::Rect(0, 0, 640, 480),
+ gfx::Size(640, 480), ::media::EmptyExtraData(),
+ ::media::EncryptionScheme::kUnencrypted)},
+ VideoPipelineClient(),
+ std::make_unique<StallingFrameProvider>(/*defer_flush=*/true)));
+
+ media_pipeline.StartPlayingFrom(base::Seconds(0));
+
+ // The audio pipeline's flush completes synchronously while the video
+ // pipeline's flush remains outstanding, so the overall flush has not yet
+ // stopped the backend.
+ bool flush_done = false;
+ media_pipeline.Flush(
+ base::BindOnce([](bool* done) { *done = true; }, &flush_done));
+ EXPECT_FALSE(flush_done);
+
+ // A StartPlayingFrom that arrives in this state must be rejected without
+ // restarting the backend or feeding any new audio buffers.
+ media_pipeline.StartPlayingFrom(base::Seconds(1));
+ task_environment.RunUntilIdle();
+}
+
} // namespace
} // namespace media
} // namespace chromecast
Regression Test / PoC
diff --git a/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc b/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
index 88acfe8..bdc5ae0 100644
--- a/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
+++ b/chromecast/media/cma/pipeline/media_pipeline_impl_unittest.cc
@@ -10,10 +10,15 @@
#include "base/functional/callback_helpers.h"
#include "base/test/task_environment.h"
#include "chromecast/media/api/test/mock_cma_backend.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "chromecast/media/cma/pipeline/av_pipeline_client.h"
#include "chromecast/media/cma/pipeline/load_type.h"
#include "chromecast/media/cma/pipeline/video_pipeline_client.h"
#include "chromecast/media/cma/test/mock_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/channel_layout.h"
#include "media/base/encryption_scheme.h"
+#include "media/base/media_util.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/base/video_decoder_config.h"
@@ -26,9 +31,40 @@
namespace media {
namespace {
+using ::testing::_;
using ::testing::AtLeast;
+using ::testing::NiceMock;
using ::testing::Return;
+// A frame provider that never delivers any frames and whose Flush completion
+// can optionally be deferred indefinitely.
+class StallingFrameProvider : public CodedFrameProvider {
+ public:
+ explicit StallingFrameProvider(bool defer_flush)
+ : defer_flush_(defer_flush) {}
+
+ StallingFrameProvider(const StallingFrameProvider&) = delete;
+ StallingFrameProvider& operator=(const StallingFrameProvider&) = delete;
+
+ ~StallingFrameProvider() override = default;
+
+ void Read(ReadCB read_cb) override { pending_read_cb_ = std::move(read_cb); }
+
+ void Flush(base::OnceClosure flush_cb) override {
+ pending_read_cb_.Reset();
+ if (defer_flush_) {
+ pending_flush_cb_ = std::move(flush_cb);
+ } else {
+ std::move(flush_cb).Run();
+ }
+ }
+
+ private:
+ const bool defer_flush_;
+ ReadCB pending_read_cb_;
+ base::OnceClosure pending_flush_cb_;
+};
+
TEST(MediaPipelineImplTest, DoesNotCrashOnFlushWhenBufferingIsDisabled) {
base::test::TaskEnvironment task_environment;
@@ -60,6 +96,70 @@
media_pipeline.Flush(base::DoNothing());
}
+// When both an audio and a video stream are present, the per-stream flushes
+// can complete independently. StartPlayingFrom must not restart the backend
+// or any AV pipeline until both have completed and the backend has been
+// stopped.
+TEST(MediaPipelineImplTest, StartPlayingFromIgnoredWhileFlushPending) {
+ base::test::TaskEnvironment task_environment;
+
+ NiceMock<MockCmaBackend::AudioDecoder> audio_decoder;
+ NiceMock<MockCmaBackend::VideoDecoder> video_decoder;
+ ON_CALL(audio_decoder, SetConfig).WillByDefault(Return(true));
+ ON_CALL(video_decoder, SetConfig).WillByDefault(Return(true));
+
+ auto backend = std::make_unique<NiceMock<MockCmaBackend>>();
+ ON_CALL(*backend, Initialize).WillByDefault(Return(true));
+ ON_CALL(*backend, Start).WillByDefault(Return(true));
+ EXPECT_CALL(*backend, CreateAudioDecoder).WillOnce(Return(&audio_decoder));
+ EXPECT_CALL(*backend, CreateVideoDecoder).WillOnce(Return(&video_decoder));
+ EXPECT_CALL(*backend, Start).Times(1);
+ EXPECT_CALL(*backend, Stop).Times(0);
+
+ EXPECT_CALL(audio_decoder, PushBuffer(_)).Times(0);
+
+ MediaPipelineImpl media_pipeline;
+ media_pipeline.Initialize(LoadType::kLoadTypeMediaStream, std::move(backend),
+ /*is_buffering_enabled=*/false);
+
+ ::media::AudioDecoderConfig audio_config(
+ ::media::AudioCodec::kMP3, ::media::kSampleFormatS16,
+ ::media::ChannelLayoutConfig::Stereo(), 44100, ::media::EmptyExtraData(),
+ ::media::EncryptionScheme::kUnencrypted);
+ ASSERT_EQ(::media::PIPELINE_OK,
+ media_pipeline.InitializeAudio(
+ audio_config, AvPipelineClient(),
+ std::make_unique<StallingFrameProvider>(
+ /*defer_flush=*/false)));
+ ASSERT_EQ(
+ ::media::PIPELINE_OK,
+ media_pipeline.InitializeVideo(
+ {::media::VideoDecoderConfig(
+ ::media::VideoCodec::kH264, ::media::H264PROFILE_MAIN,
+ ::media::VideoDecoderConfig::AlphaMode::kIsOpaque,
+ ::media::VideoColorSpace(), ::media::kNoTransformation,
+ gfx::Size(640, 480), gfx::Rect(0, 0, 640, 480),
+ gfx::Size(640, 480), ::media::EmptyExtraData(),
+ ::media::EncryptionScheme::kUnencrypted)},
+ VideoPipelineClient(),
+ std::make_unique<StallingFrameProvider>(/*defer_flush=*/true)));
+
+ media_pipeline.StartPlayingFrom(base::Seconds(0));
+
+ // The audio pipeline's flush completes synchronously while the video
+ // pipeline's flush remains outstanding, so the overall flush has not yet
+ // stopped the backend.
+ bool flush_done = false;
+ media_pipeline.Flush(
+ base::BindOnce([](bool* done) { *done = true; }, &flush_done));
+ EXPECT_FALSE(flush_done);
+
+ // A StartPlayingFrom that arrives in this state must be rejected without
+ // restarting the backend or feeding any new audio buffers.
+ media_pipeline.StartPlayingFrom(base::Seconds(1));
+ task_environment.RunUntilIdle();
+}
+
} // namespace
} // namespace media
} // namespace chromecast
Original Bug Report
Potential Cast browser UAF in AvPipelineImpl due to renderer-controlled per-stream Flush asymmetry
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential Use-After-Free (UAF) vulnerability exists in the Chromecast media pipeline due to an incorrect state-machine assumption during stream flushing. By selectively responding to flush requests, a compromised renderer can drive one stream pipeline to a flushed state while the other is still flushing, leaving the backend running. If a play request is subsequently issued, the flushed pipeline will release its references to the active buffers while the running backend continues to access them.
Affected files:
chromecast/media/cma/pipeline/media_pipeline_impl.ccchromecast/media/cma/pipeline/av_pipeline_impl.ccchromecast/media/cma/base/demuxer_stream_adapter.ccchromecast/media/common/audio_decoder_wrapper.ccchromecast/media/common/audio_decoder_software_wrapper.cc
Estimated timestamp from git blame: 2026-05-14
Description
An issue has been identified in the Chromecast media pipeline (MediaPipelineImpl and AvPipelineImpl) where a compromised renderer can cause a desynchronization of the stream flushing states, leading to a potential Use-After-Free (UAF) of reference-counted audio/video buffers in the browser process.
To prevent double-start scenarios, a state guard exists in AvPipelineImpl::StartPlayingFrom to ensure the stream is in the kFlushed state:
if (state_ != kFlushed) {
LOG(ERROR) << __FUNCTION__ << " called in unexpected state " << state_;
return false;
}
This assumes that if the stream is in the kFlushed state, the backend has been safely stopped and all currently pending buffers have been released. However, in MediaPipelineImpl::OnFlushDone, the media backend is only stopped when both the audio and video streams have finished flushing:
if (pending_flush_task_->audio_flushed &&
pending_flush_task_->video_flushed) {
media_pipeline_backend_->Stop();
backend_state_ = BACKEND_STATE_INITIALIZED;
...
}
Because per-stream flush completion is gated on the renderer answering an outstanding mojom::DemuxerStream::Read, a compromised renderer can selectively answer the audio read while stalling the video read. This drives the audio pipeline to the kFlushed state while the video pipeline remains in kFlushing. At this point, the backend is not stopped, and continues to hold a raw CastDecoderBuffer* pointer to the active audio buffer (Buffer A) under the kBufferPending contract.
In a release build, if the renderer subsequently sends a StartPlayingFrom IPC, the safety guard DCHECK(!pending_flush_task_) is stripped. The audio pipeline passes its state_ == kFlushed check, discards its reference to Buffer A (pushed_buffer_ = nullptr), and posts a FetchBuffer task. The video pipeline fails the state check and returns false, triggering OnError, but this does not stop the backend or cancel the posted audio FetchBuffer task.
When the new Buffer B is retrieved and pushed via ActiveAudioDecoderWrapper::PushBuffer (or AudioDecoderSoftwareWrapper::PushBuffer), pushed_buffer_ = std::move(buffer) overwrites the reference to Buffer A. Since this is the last scoped_refptr holding Buffer A, it is deallocated. The media backend, which was never stopped, still holds the raw pointer to Buffer A, resulting in a Use-After-Free when it next dereferences it.
Note: Our tooling currently lacks the capability to compile and execute a functional proof-of-concept exploit, so these steps are suggested based on static analysis of the source code.
Potential Trigger Path / Attack Scenario
- A compromised renderer initializes a
CastRendererwith both audio and video streams, and starts playing. - The renderer answers the first audio Read with Buffer A, returning
kBufferPendingand causing the backend to store a raw pointer to Buffer A. - The renderer stalls both the audio and video read-ahead requests.
- The renderer triggers a flush via
mojom::Renderer::Flush. Both pipelines enter thekFlushingstate. - The renderer answers only the audio read request. This transitions the audio pipeline to
kFlushed. However,MediaPipelineImpl::OnFlushDonedoes not stop the backend because the video pipeline is still flushing. - The renderer sends a
StartPlayingFromIPC request. The audio pipeline passes the state guard, drops its reference to Buffer A, and schedules aFetchBuffertask. The video pipeline fails the guard, which triggersOnErrorbut fails to stop the backend or the posted fetch task. - The audio pipeline receives a new Buffer B. When pushing Buffer B, the last reference to Buffer A is overwritten and Buffer A is freed.
- The backend eventually dereferences the raw pointer to the freed Buffer A, causing a Use-After-Free.
Impact
This leads to a Use-After-Free of a polymorphic reference-counted object (DecoderBufferBase / CastDecoderBuffer) in the unsandboxed Cast browser process (cast_shell on CastOS). An attacker who has compromised the renderer can potentially leverage this to execute arbitrary code with browser-process privileges, escaping the sandbox. On CastOS, the PartitionAllocBackupRefPtr (MiraclePtr) mitigation is disabled, leaving the vulnerability potentially fully exploitable.
Affected Files
chromecast/media/cma/pipeline/media_pipeline_impl.ccchromecast/media/cma/pipeline/av_pipeline_impl.ccchromecast/media/cma/base/demuxer_stream_adapter.ccchromecast/media/common/audio_decoder_wrapper.ccchromecast/media/common/audio_decoder_software_wrapper.cc
Suggested Fix
To prevent this state-machine bypass in production builds, enforce the flush task check in release builds by returning early or throwing an error in MediaPipelineImpl::StartPlayingFrom if a flush task is pending, rather than relying solely on a DCHECK:
if (pending_flush_task_) {
LOG(ERROR) << "StartPlayingFrom called during pending flush";
OnError(::media::PIPELINE_ERROR_ABORT);
return;
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.