Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactSide-channel information leakage in Media
DescriptionSide-channel information leakage in Media
ComponentMedia
Bug ClassLogic Error
Tracker525691898
Fix commit994c22a4f01c (chromium/src) +495/-93
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
media/filters/hls_data_source_provider.cc
modified
HlsNetworkAccessImpl
media/filters/hls_network_access_impl.cc
modified
if
media/filters/hls_network_access_impl.cc
modified

Files Changed

  • media/filters/hls_data_source_provider.cc
  • media/filters/hls_data_source_provider.h
  • media/filters/hls_network_access_impl.cc
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.