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 <tmathmeyer@chromium.org>
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 <eugene@chromium.org>
Commit-Queue: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
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