Chrome · Media
CVE-2026-18019
Logic Error in Media
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/filters/hls_data_source_provider.cc |
modified | |
HlsNetworkAccessImplmedia/filters/hls_network_access_impl.cc |
modified | |
ifmedia/filters/hls_network_access_impl.cc |
modified |
Files Changed
media/filters/hls_data_source_provider.ccmedia/filters/hls_data_source_provider.hmedia/filters/hls_network_access_impl.cc
Patch
From 994c22a4f01c31cfb84155dbe7361f142996a358 Mon Sep 17 00:00:00 2001 From: Ted Meyer <[email protected]> Date: Thu, 25 Jun 2026 11:09:54 -0700 Subject: [PATCH] [HLS] Fetch headers, segments, and keys simultaneously Fetching these things one-at-a-time was causing an small information leak where the presence of a latter request would indicate that a prior request was successful. Requesting them all in parallel removes this issue. Bug: 525691898 Change-Id: I78677034cdaa7c9e836da3feb93cf514862428ad Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7988495 Reviewed-by: Eugene Zemtsov <[email protected]> Commit-Queue: Ted (Chromium) Meyer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1652559} --- diff --git a/media/filters/hls_data_source_provider.cc b/media/filters/hls_data_source_provider.cc index 7fb5850e..ec6fcea 100644 --- a/media/filters/hls_data_source_provider.cc +++ b/media/filters/hls_data_source_provider.cc @@ -44,6 +44,20 @@ security_info_.MergeFrom(other); } +void HlsDataSourceStream::PrependInitStream( + std::unique_ptr<HlsDataSourceStream> init_stream) { + CHECK(!stream_locked_); + CHECK(init_stream); + size_t init_size = init_stream->buffer_.size(); + if (init_size == 0) { + return; + } + buffer_.insert(buffer_.begin(), init_stream->buffer_.begin(), + init_stream->buffer_.end()); + write_index_ += init_size; + MergeSecurityMetadata(init_stream->security_info_); +} + void HlsDataSourceStream::TrackOrigin(const url::Origin& origin) { security_info_.response_origins.insert(origin); } diff --git a/media/filters/hls_data_source_provider.h b/media/filters/hls_data_source_provider.h index 2d833ee9d..ed5107c 100644 --- a/media/filters/hls_data_source_provider.h +++ b/media/filters/hls_data_source_provider.h @@ -124,6 +124,10 @@ // origin sets and merges the security flags. void MergeSecurityMetadata(const hls::SecurityMetadata& other); + // Prepend another stream's data and merge its security metadata. + // This is used for parallel fetching of init segment and media segment. + void PrependInitStream(std::unique_ptr<HlsDataSourceStream> init_stream); + // A stream's origin is considered tainted if any backing data source involved // in this playback is tainted. void set_would_taint_origin() { security_info_.would_taint_origin = true; } diff --git a/media/filters/hls_network_access_impl.cc b/media/filters/hls_network_access_impl.cc index 3990a34..0d45836 100644 --- a/media/filters/hls_network_access_impl.cc +++ b/media/filters/hls_network_access_impl.cc @@ -8,26 +8,187 @@ namespace media { -namespace { +class HlsNetworkAccessImpl::ParallelFetchState + : public base::RefCountedThreadSafe<ParallelFetchState> { + public: + ParallelFetchState(base::WeakPtr<HlsNetworkAccessImpl> network_access, + url::Origin manifest_origin, + scoped_refptr<hls::MediaSegment::EncryptionData> enc_data, + HlsDataSourceProvider::ReadCb cb) + : network_access_(std::move(network_access)), + manifest_origin_(std::move(manifest_origin)), + enc_data_(std::move(enc_data)), + completion_cb_(std::move(cb)) {} -void MergeEncryptionSecurityMetadata( - scoped_refptr<hls::MediaSegment::EncryptionData> enc_data, - HlsDataSourceProvider::ReadCb cb, - HlsDataSourceProvider::ReadResult result) { - if (!result.has_value()) { - std::move(cb).Run(std::move(result).error().AddHere()); - return; + void Start(std::optional<GURL> key_uri, + std::optional<HlsDataSourceProvider::UrlDataSegment> init_segment, + HlsDataSourceProvider::UrlDataSegment media_segment, + bool read_chunked) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (key_uri) { + key_pending_ = true; + if (network_access_) { + network_access_->ReadAllInternal( + *key_uri, base::BindOnce(&ParallelFetchState::OnKeyLoaded, this)); + } + } + + if (init_segment) { + init_pending_ = true; + if (network_access_) { + HlsDataSourceProvider::SegmentQueue queue; + queue.push(*std::move(init_segment)); + + auto cb = base::BindOnce(&ParallelFetchState::OnInitLoaded, this); + cb = base::BindOnce(&HlsNetworkAccessImpl::ReadUntilExhaustedHelper, + network_access_, std::move(cb)); + + network_access_->ReadSegmentQueueInternal(std::move(queue), + std::move(cb)); + } + } + + segment_pending_ = true; + if (network_access_) { + HlsDataSourceProvider::SegmentQueue queue; + queue.push(std::move(media_segment)); + + auto cb = base::BindOnce(&ParallelFetchState::OnSegmentLoaded, this); + if (!read_chunked) { + cb = base::BindOnce(&HlsNetworkAccessImpl::ReadUntilExhaustedHelper, + network_access_, std::move(cb)); + } + network_access_->ReadSegmentQueueInternal(std::move(queue), + std::move(cb)); + } } - auto stream = std::move(result).value(); - const auto& encryption_metadata = enc_data->GetSecurityMetadata(); - if (encryption_metadata.has_value()) { - stream->MergeSecurityMetadata(*encryption_metadata); - } - std::move(cb).Run(std::move(stream)); -} + private: + friend class base::RefCountedThreadSafe<ParallelFetchState>; + ~ParallelFetchState() = default; -} // namespace + void OnKeyLoaded(HlsDataSourceProvider::ReadResult result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (aborted_) { + return; + } + key_pending_ = false; + key_result_ = std::move(result); + if (!key_result_->has_value()) { + OnError(std::move(*key_result_).error()); + return; + } + CheckCompleted(); + } + + void OnInitLoaded(HlsDataSourceProvider::ReadResult result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (aborted_) { + return; + } + init_pending_ = false; + init_result_ = std::move(result); + if (!init_result_->has_value()) { + OnError(std::move(*init_result_).error()); + return; + } + CheckCompleted(); + } + + void OnSegmentLoaded(HlsDataSourceProvider::ReadResult result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (aborted_) { + return; + } + segment_pending_ = false; + segment_result_ = std::move(result); + if (!segment_result_->has_value()) { + OnError(std::move(*segment_result_).error()); + return; + } + CheckCompleted(); + } + + void OnError(HlsDataSourceProvider::ReadStatus status) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (aborted_) { + DCHECK(!completion_cb_); + return; + } + aborted_ = true; + if (completion_cb_) { + std::move(completion_cb_).Run(std::move(status)); + } + } + + void CheckCompleted() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (aborted_) { + return;
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/media/filters/hls_network_access_impl_unittest.cc b/media/filters/hls_network_access_impl_unittest.cc
index 6078b2a..5c4f9eba 100644
--- a/media/filters/hls_network_access_impl_unittest.cc
+++ b/media/filters/hls_network_access_impl_unittest.cc
@@ -262,16 +262,38 @@
TEST_F(HlsNetworkAccessImplUnittest, TestSegmentReadNoChunk) {
auto segment = MakeSegment(std::nullopt, std::make_tuple(100000, 100),
InitMode::kPresent);
- factory_->AddReadExpectation(100, 16384, 16384);
+
+ const GURL init_uri("https://foo.com");
+ const GURL media_uri("https://example.com/content.mp4");
+
+ EXPECT_CALL(*factory_, Setup(_, init_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(100, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(100));
+ EXPECT_CALL(*mock, Read(200, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(0));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(500));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
network_access_->ReadMediaSegment(
*segment, /*read_chunked=*/true, /*include_init_segment=*/true,
base::BindOnce([&](HlsDataSourceProvider::ReadResult result) {
ASSERT_TRUE(result.has_value());
auto stream = std::move(result).value();
- ASSERT_EQ(stream->read_position(), 16484lu);
- ASSERT_EQ(stream->buffer_size(), 16384lu);
- ASSERT_EQ(stream->max_read_position(), 100100lu);
+ ASSERT_EQ(stream->read_position(), 500lu);
+ ASSERT_EQ(stream->buffer_size(), 16484lu);
+ ASSERT_EQ(stream->max_read_position(), std::nullopt);
ASSERT_TRUE(stream->CanReadMore());
}));
task_environment_.RunUntilIdle();
@@ -526,4 +548,243 @@
task_environment_.RunUntilIdle();
}
+TEST_F(HlsNetworkAccessImplUnittest, TestSegmentReadInitConnectionFailure) {
+ auto segment =
+ MakeSegment(std::nullopt, std::make_tuple(100, 100), InitMode::kPresent);
+
+ const GURL init_uri("https://foo.com");
+ const GURL media_uri("https://example.com/content.mp4");
+
+ // Init segment fails to connect
+ EXPECT_CALL(*factory_, Setup(_, init_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsFailure(mock);
+ });
+
+ // Media segment succeeds to connect, and we mock its read.
+ // Even if Init fails, Media might still be created and read in parallel.
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(500));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ bool callback_called = false;
+ network_access_->ReadMediaSegment(
+ *segment, /*read_chunked=*/true, /*include_init_segment=*/true,
+ base::BindOnce(
+ [](bool* cb_called, HlsDataSourceProvider::ReadResult result) {
+ *cb_called = true;
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(std::move(result).error().code(),
+ HlsDataSourceProvider::ReadStatus::Codes::kStopped);
+ },
+ &callback_called));
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(callback_called);
+}
+
+TEST_F(HlsNetworkAccessImplUnittest, TestSegmentReadMediaReadFailure) {
+ auto segment =
+ MakeSegment(std::nullopt, std::make_tuple(100, 100), InitMode::kPresent);
+
+ const GURL init_uri("https://foo.com");
+ const GURL media_uri("https://example.com/content.mp4");
+
+ // Init segment succeeds
+ EXPECT_CALL(*factory_, Setup(_, init_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(100, SpanSizeEq(100), _))
+ .WillOnce(base::test::RunOnceCallback<2>(100));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ // Media segment fails read
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(DataSource::kReadError));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ bool callback_called = false;
+ network_access_->ReadMediaSegment(
+ *segment, /*read_chunked=*/true, /*include_init_segment=*/true,
+ base::BindOnce(
+ [](bool* cb_called, HlsDataSourceProvider::ReadResult result) {
+ *cb_called = true;
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(std::move(result).error().code(),
+ HlsDataSourceProvider::ReadStatus::Codes::kError);
+ },
+ &callback_called));
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(callback_called);
+}
+
+TEST_F(HlsNetworkAccessImplUnittest,
+ TestSegmentReadInitReadFailureMediaSucceeds) {
+ auto segment =
+ MakeSegment(std::nullopt, std::make_tuple(100, 100), InitMode::kPresent);
+
+ const GURL init_uri("https://foo.com");
+ const GURL media_uri("https://example.com/content.mp4");
+
+ // Init segment fails read
+ EXPECT_CALL(*factory_, Setup(_, init_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(100, SpanSizeEq(100), _))
+ .WillOnce(base::test::RunOnceCallback<2>(DataSource::kReadError));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ // Media segment succeeds
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(500));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ bool callback_called = false;
+ network_access_->ReadMediaSegment(
+ *segment, /*read_chunked=*/true, /*include_init_segment=*/true,
+ base::BindOnce(
+ [](bool* cb_called, HlsDataSourceProvider::ReadResult result) {
+ *cb_called = true;
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(std::move(result).error().code(),
+ HlsDataSourceProvider::ReadStatus::Codes::kError);
+ },
+ &callback_called));
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(callback_called);
+}
+
+TEST_F(HlsNetworkAccessImplUnittest, TestSegmentReadKeyFailure) {
+ auto segment = MakeSegment(std::nullopt, std::nullopt, InitMode::kAbsent,
+ "https://example.com/enc.key");
+
+ const GURL key_uri("https://example.com/enc.key");
+ const GURL media_uri("https://example.com/content.mp4");
+
+ // Key segment fails read
+ EXPECT_CALL(*factory_, Setup(_, key_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(DataSource::kReadError));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ // Media segment succeeds (but it might be aborted/ignored after key fails)
+ // Actually, they start in parallel. Key and Media.
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(500));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ bool callback_called = false;
+ network_access_->ReadMediaSegment(
+ *segment, /*read_chunked=*/true, /*include_init_segment=*/true,
+ base::BindOnce(
+ [](bool* cb_called, HlsDataSourceProvider::ReadResult result) {
+ *cb_called = true;
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(std::move(result).error().code(),
+ HlsDataSourceProvider::ReadStatus::Codes::kError);
+ },
+ &callback_called));
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(callback_called);
+}
+
+TEST_F(HlsNetworkAccessImplUnittest, TestSegmentReadKeyFailureLater) {
+ auto segment = MakeSegment(std::nullopt, std::make_tuple(100, 100),
+ InitMode::kPresent, "https://example.com/enc.key");
+
+ const GURL init_uri("https://foo.com");
+ const GURL media_uri("https://example.com/content.mp4");
+ const GURL key_uri("https://example.com/enc.key");
+
+ DataSource::ReadCB key_read_cb;
+
+ // Key segment Setup. It will capture the ReadCB and NOT run it immediately.
+ EXPECT_CALL(*factory_, Setup(_, key_uri, _, _))
+ .WillOnce([&](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce([&](int64_t, base::span<uint8_t>, DataSource::ReadCB cb) {
+ key_read_cb = std::move(cb);
+ });
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ // Init segment Setup. Succeeds synchronously.
+ EXPECT_CALL(*factory_, Setup(_, init_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(100, SpanSizeEq(100), _))
+ .WillOnce(base::test::RunOnceCallback<2>(100));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ // Media segment Setup. Succeeds synchronously.
+ EXPECT_CALL(*factory_, Setup(_, media_uri, _, _))
+ .WillOnce([](MockDataSource* mock, const GURL& uri, ...) {
+ MockDataSourceFactory::ConfigureAsSuccess(mock, uri);
+ EXPECT_CALL(*mock, Read(0, SpanSizeEq(16384), _))
+ .WillOnce(base::test::RunOnceCallback<2>(500));
+ EXPECT_CALL(*mock, WouldTaintOrigin())
+ .WillRepeatedly(testing::Return(false));
+ });
+
+ bool callback_called = false;
+ network_access_->ReadMediaSegment(
+ *segment, /*read_chunked=*/true, /*include_init_segment=*/true,
+ base::BindOnce(
+ [](bool* cb_called, HlsDataSourceProvider::ReadResult result) {
+ *cb_called = true;
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(std::move(result).error().code(),
+ HlsDataSourceProvider::ReadStatus::Codes::kError);
+ },
+ &callback_called));
+
+ // Run until idle. This will run Key Setup (capturing callback),
+ // and run Init and Media Setup and their reads to completion.
+ task_environment_.RunUntilIdle();
+
+ // The overall callback should NOT have run yet because Key is still pending.
+ EXPECT_FALSE(callback_called);
+ ASSERT_TRUE(key_read_cb);
+
+ // Now fail the key read.
+ std::move(key_read_cb).Run(DataSource::kReadError);
+
+ // Run until idle again to process the key failure and trigger overall
+ // callback.
+ task_environment_.RunUntilIdle();
+
+ EXPECT_TRUE(callback_called);
+}
+
} // namespace media
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