High chrome Info Disclosure 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Media
DescriptionMemory corruption in Media
ComponentMedia
Sub-componentHLS
Bug ClassInfo Disclosure
Tracker572878518
Fix commiteda3f57fe1a0 (chromium/src) +35/-6
CISA KEVNot listed
CreditedPlease attribute Anthropic and Ada Logics.
Disclosed2026-10-27

Background

HLS (HTTP Live Streaming)
a manifest-driven streaming format. Chromium’s media/filters/hls_data_source_provider_impl.cc fetches the playlist and its segments, following redirects and tracking the origin of fetched data.
`file://` same-origin rules
pages/scripts loaded from file:// may load media directly via src=, but the network stack forbids arbitrary cross-file reads (e.g. fetch() of another file:// URL) to prevent local file exfiltration.
Range requests / variable substitution
HLS can issue byte-range requests to segments and supports programmatic/variable manipulation of segment URIs, giving script-driven, byte-granular access to a target resource.
`HlsDemuxerStatus`
the demuxer’s status enum; the patch adds kInvalidContentScheme to reject disallowed schemes.
`CrossOriginFileDataSourceWrapper`
a test-only data source that, post-patch, can report a synthetic HTTPS origin while reading from a local file (used so tests keep working after file:// HLS is banned).

Root Cause Analysis

The advisory calls this “Memory corruption in Media” (MemoryCorruption), but the diff and commit message describe an information-disclosure / same-origin-policy bypass, not a memory-safety bug — there is no corrupted buffer anywhere in the change. The root problem: HLS gave a file://-origin page a programmatic, byte-range read capability over arbitrary file:// URLs. A local HTML file opened via file:// is normally barred from fetch()-ing other local files, but because HLS resolves manifest/segment URIs (including via variable substitution) and performs range requests, a malicious local playlist could point segments at any other file:// path and probe/read it byte-by-byte through playback, effectively reconstructing an entire local file — defeating the file:// read restriction the browser enforces everywhere else.

The fix closes the vector in HlsDataSourceProviderImpl by inspecting the (post-redirect) response URI: if response_uri.SchemeIs("file"), it immediately fails the request with HlsDemuxerStatus::Codes::kInvalidContentScheme, ends the trace event, and returns before SetPostRedirectUri(). In other words, HLS is now forbidden from loading any file:// URL at all — the commit notes it was already impossible to mix file:// with other schemes, so this breaks no legitimate use. Supporting changes add the kInvalidContentScheme enum value (and its message/metric), and rework the integration-test harness so CrossOriginFileDataSourceWrapper serves local test data under a synthetic https://media.com/... origin (GetUrlDataOrigin()/SetUrlOrigin()), since tests previously relied on file:// manifests that are now blocked.

Key insight
HLS let a file://-origin page make variable-driven, byte-range requests to arbitrary other file:// URLs, turning playback into a local-file read primitive that bypassed the browser’s ban on cross-file reads; the fix simply refuses any file:// scheme in the HLS data source.

Attack Path

  1. Deliver a local HTML file the attacker gets the victim to open a malicious .html from disk (download, email attachment, archive) so it runs with a file:// origin (inferred: requires the victim to open a local file).
  2. Load a crafted HLS manifest the page starts HLS playback on a file:// playlist it controls.
  3. Target other local files using HLS variable substitution / segment URIs, the manifest references arbitrary file:// paths (e.g. ~/.ssh/..., documents) as segments.
  4. Byte-range probing HLS issues range requests and reports playability/decoding results per range, giving the script byte-granular feedback about the target file’s contents.
  5. Reconstruct the file repeated range reads collectively exfiltrate the full contents of the targeted local file to the file:// page.
  6. Primitive arbitrary local-file disclosure to script that should have no cross-file read access; no memory corruption is involved.

Impact Assessment

This is an information-disclosure / same-origin-policy bypass in the media pipeline, reachable only once a victim opens an attacker-controlled file:// page (a meaningful precondition that lowers real-world severity compared to a remote web attack). No elevated OS privilege is needed; the gained primitive is read access to arbitrary local files from a file://-origin document, which the browser otherwise forbids. The advisory’s “memory corruption” label is not substantiated by the diff — the code change is a scheme allow/deny decision with no buffer or lifetime involved — so the honest classification is InfoDisclosure (with a strong PolicyBypass flavor). Confidence that the mechanism is info disclosure (not memory safety) is high given the explicit commit rationale.

Files Changed

  • media/filters/hls_demuxer_status.h
  • media/test/pipeline_integration_test_base.cc
  • tools/metrics/histograms/enums.xml
  • media/filters/hls_data_source_provider_impl.cc

Audit Directions

  • Scheme checks in media loaders
    audit other demuxers/data-source providers (DASH, MSE segment fetchers) for whether they can resolve or redirect to file:// URLs from a file://-origin document.
  • Programmatic range-read vectors
    look for other features that expose byte-range feedback over attacker-named resources (range requests reflected into observable timing/decode results) as local-file oracle primitives.
  • Variable/URI substitution
    review HLS (and similar) manifest variable substitution for other ways to synthesize unexpected URIs or schemes post-parse.
  • Advisory-vs-code mismatch
    “Memory corruption” misclassifies this SOP/info-disclosure fix; check sibling Media CVEs for the same generic labeling.
From eda3f57fe1a0940f44a1b32520bfa950dba37cf5 Mon Sep 17 00:00:00 2001
From: Ted Meyer <tmathmeyer@chromium.org>
Date: Thu, 15 Oct 2026 18:11:33 -0700
Subject: [PATCH] [HLS] Disallow file:// HLS content

pages and scripts loaded from the file:// scheme aren't allowed to read
arbitrary data from other file:// urls (ie, you can't use fetch!), but
HLS was a vector around that, since it allows somewhat programmatic
access to individual bytes of a remote file, which could collectively
allow reading an entire local file.

As a result, the only safe way to stop this is to disable hls content
from loading any file:// urls. It was already impossible to mix file://
and other schemes anyway.

The only real place this was useful was ironically testing, which means
that we have to make some tweaks to the CrossOriginFileDataSourceWrapper
implementation to lie about the URL origin.

Fixed: 572878518
Change-Id: I181db95da42ac7eaa35a655754405efe78ed8b91
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8587477
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1720224}
---
diff --git a/media/filters/hls_data_source_provider_impl.cc b/media/filters/hls_data_source_provider_impl.cc
index 4e1f8b7..a317928 100644
--- a/media/filters/hls_data_source_provider_impl.cc
+++ b/media/filters/hls_data_source_provider_impl.cc
@@ -203,6 +203,23 @@
       if (!response_uri.SchemeIs("data")) {
         stream->TrackOrigin(url::Origin::Create(response_uri));
       }
+      if (response_uri.SchemeIs("file")) {
+        // For a base page loaded via the file:// protocol, the chrome network
+        // stack allows media content loaded directly via src=, but for security
+        // reasons, does not allow arbitrary reads from other file:// urls (ie,
+        // via JS using `fetch()`).
+        // Since HLS provides the ability to play local manifests with variable
+        // substitution, a file:// hosted playlist could theoretically make
+        // range requests to any arbitrary other file:// uri and test for
+        // playback.
+
+        // As a result, the only thing we can do is block HLS from using the
+        // file:// protocol entirely.
+        std::move(callback).Run(base::unexpected(
+            HlsDemuxerStatus{HlsDemuxerStatus::Codes::kInvalidContentScheme}));
+        TRACE_EVENT_END("media", GetTracingTrack(this));
+        return;
+      }
       stream->SetPostRedirectUri(response_uri);
     } else {
       HlsDemuxerStatus error = {HlsDemuxerStatus::Codes::kNetworkReadError,
diff --git a/media/filters/hls_demuxer_status.h b/media/filters/hls_demuxer_status.h
index 5793cbf..bc999191 100644
--- a/media/filters/hls_demuxer_status.h
+++ b/media/filters/hls_demuxer_status.h
@@ -76,8 +76,11 @@
     // error during initialization.
     kNoDataEverAppended = 18,
 
+    // the file:// data scheme is not allowed in HLS.
+    kInvalidContentScheme = 19,
+
     // Keep this at the end and equal to the highest value ever used.
-    kMaxValue = kNoDataEverAppended,
+    kMaxValue = kInvalidContentScheme,
   };
 
   static constexpr StatusGroupType Group() { return "HlsDemuxerStatus"; }
@@ -122,6 +125,8 @@
         return "Could not append data to ChunkDemuxer";
       case Codes::kNoDataEverAppended:
         return "Segment queue never had any data";
+      case Codes::kInvalidContentScheme:
+        return "The content scheme is disallowed in HLS.";
     }
   }
 };
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index d37ebd4f..de7baa9 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4889,6 +4889,7 @@
   <int value="16" label="kInvalidLoadedRanges"/>
   <int value="17" label="kCouldNotAppendData"/>
   <int value="18" label="kNoDataEverAppended"/>
+  <int value="19" label="kInvalidContentScheme"/>
 </enum>
 
 <enum name="HpsTurnOnResult">
diff --git a/media/test/pipeline_integration_test_base.cc b/media/test/pipeline_integration_test_base.cc
index ab17286b..c28294ae 100644
--- a/media/test/pipeline_integration_test_base.cc
+++ b/media/test/pipeline_integration_test_base.cc
@@ -130,6 +130,8 @@
   }
   bool DidRedirect() const override { return file_data_source_.DidRedirect(); }
   GURL GetUrlAfterRedirects() const override { return url_; }
+  GURL GetUrlDataOrigin() const override { return origin_; }
+  void SetUrlOrigin(GURL uri) { origin_ = std::move(uri); }
   void StopPreloading() override { file_data_source_.StopPreloading(); }
   void OnMediaPlaybackRateChanged(double playback_rate) override {
     file_data_source_.OnMediaPlaybackRateChanged(playback_rate);
@@ -160,6 +162,7 @@
   }
 
  private:
+  GURL origin_;
   GURL url_;
   FileDataSource file_data_source_;
 };
@@ -174,13 +177,16 @@
                   callback) override {
     auto file_data_source =
         std::make_unique<CrossOriginFileDataSourceWrapper>();
+    file_data_source->SetUrlOrigin(uri);
     base::FilePath file_path(
 #if BUILDFLAG(IS_WIN)
         // Windows file paths can't start with '/' the way unix file paths can,
-        // So we have to strip the leading one which comes from GetContent().
-        base::UTF8ToWide(uri.GetContent().erase(0, 1))
+        // So we have to strip the leading `media.com/` from GetContent().
+        base::UTF8ToWide(uri.GetContent().erase(0, 10))
 #else
-        uri.GetContent()
+        // Linux/Mac file paths can start with '/' so we have to strip only the
+        // leading `media.com`.
+        uri.GetContent().erase(0, 9)
 #endif
     );
     CHECK(file_data_source->Initialize(file_path))
@@ -394,8 +400,8 @@
   hashing_enabled_ = true;
 
   auto full_path = GetTestDataFilePath(filename);
-  std::string file_url = "file://" + full_path.MaybeAsASCII();
-  GURL manifest_root{file_url};
+  std::string file_url = full_path.MaybeAsASCII();
+  GURL manifest_root{"https://media.com/" + file_url};
 
   auto multibuffer_factory = std::make_unique<TestDataSourceFactory>();
   // HlsManifestDemuxerEngine requires a SequenceBound data source provider,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/test/pipeline_integration_test_base.cc b/media/test/pipeline_integration_test_base.cc
index ab17286b..c28294ae 100644
--- a/media/test/pipeline_integration_test_base.cc
+++ b/media/test/pipeline_integration_test_base.cc
@@ -130,6 +130,8 @@
   }
   bool DidRedirect() const override { return file_data_source_.DidRedirect(); }
   GURL GetUrlAfterRedirects() const override { return url_; }
+  GURL GetUrlDataOrigin() const override { return origin_; }
+  void SetUrlOrigin(GURL uri) { origin_ = std::move(uri); }
   void StopPreloading() override { file_data_source_.StopPreloading(); }
   void OnMediaPlaybackRateChanged(double playback_rate) override {
     file_data_source_.OnMediaPlaybackRateChanged(playback_rate);
@@ -160,6 +162,7 @@
   }
 
  private:
+  GURL origin_;
   GURL url_;
   FileDataSource file_data_source_;
 };
@@ -174,13 +177,16 @@
                   callback) override {
     auto file_data_source =
         std::make_unique<CrossOriginFileDataSourceWrapper>();
+    file_data_source->SetUrlOrigin(uri);
     base::FilePath file_path(
 #if BUILDFLAG(IS_WIN)
         // Windows file paths can't start with '/' the way unix file paths can,
-        // So we have to strip the leading one which comes from GetContent().
-        base::UTF8ToWide(uri.GetContent().erase(0, 1))
+        // So we have to strip the leading `media.com/` from GetContent().
+        base::UTF8ToWide(uri.GetContent().erase(0, 10))
 #else
-        uri.GetContent()
+        // Linux/Mac file paths can start with '/' so we have to strip only the
+        // leading `media.com`.
+        uri.GetContent().erase(0, 9)
 #endif
     );
     CHECK(file_data_source->Initialize(file_path))
@@ -394,8 +400,8 @@
   hashing_enabled_ = true;
 
   auto full_path = GetTestDataFilePath(filename);
-  std::string file_url = "file://" + full_path.MaybeAsASCII();
-  GURL manifest_root{file_url};
+  std::string file_url = full_path.MaybeAsASCII();
+  GURL manifest_root{"https://media.com/" + file_url};
 
   auto multibuffer_factory = std::make_unique<TestDataSourceFactory>();
   // HlsManifestDemuxerEngine requires a SequenceBound data source provider,
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.