CVE-2026-118327
Overview
Background
- HLS (HTTP Live Streaming)
- a manifest-driven streaming format. Chromium’s
media/filters/hls_data_source_provider_impl.ccfetches 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 viasrc=, but the network stack forbids arbitrary cross-file reads (e.g.fetch()of anotherfile://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
kInvalidContentSchemeto 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.
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
- Deliver a local HTML file
the attacker gets the victim to open a malicious
.htmlfrom disk (download, email attachment, archive) so it runs with afile://origin (inferred: requires the victim to open a local file). - Load a crafted HLS manifest
the page starts HLS playback on a
file://playlist it controls. - Target other local files
using HLS variable substitution / segment URIs, the manifest references arbitrary
file://paths (e.g.~/.ssh/..., documents) as segments. - 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.
- Reconstruct the file
repeated range reads collectively exfiltrate the full contents of the targeted local file to the
file://page. - Primitive arbitrary local-file disclosure to script that should have no cross-file read access; no memory corruption is involved.
Impact Assessment
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.hmedia/test/pipeline_integration_test_base.cctools/metrics/histograms/enums.xmlmedia/filters/hls_data_source_provider_impl.cc
Audit Directions
- Scheme checks in media loadersaudit other demuxers/data-source providers (DASH, MSE segment fetchers) for whether they can resolve or redirect to
file://URLs from afile://-origin document. - Programmatic range-read vectorslook 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 substitutionreview 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.
Patch
From eda3f57fe1a0940f44a1b32520bfa950dba37cf5 Mon Sep 17 00:00:00 2001 From: Ted Meyer <[email protected]> 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 <[email protected]> Commit-Queue: Ted (Chromium) Meyer <[email protected]> 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,
Regression Test / PoC
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,