Commit 4504a474 authored by Fredrik Hubinette's avatar Fredrik Hubinette Committed by Commit Bot

defeat cors attacks on audio/video tags

Neutralize error messages and fire no progress events
until media metadata has been loaded for media loaded
from cross-origin locations.

Bug: 828265, 826187
Change-Id: Iaf15ef38676403687d6a913cbdc84f2d70a6f5c6
Reviewed-on: https://chromium-review.googlesource.com/1015794Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Commit-Queue: Fredrik Hubinette <hubbe@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557312}
parent 4a022e6e
<!DOCTYPE html>
<title>Check that crossorigin media requests don't reveal information about non-media files.</title>
<script src="/w3c/resources/testharness.js"></script>
<script src="/w3c/resources/testharnessreport.js"></script>
<script src="/media-resources/media-file.js"></script>
<video></video>
<script>
promise_test(async function() {
function get_all_events(url) {
return new Promise(function(resolve, reject) {
let events = [];
let current_state = -1;
let current_buffered_string = "";
const video = document.querySelector("video");
function pollTagState(prefix) {
const state = video.networkState;
if (state != current_state) {
current_state = state
events.push(prefix + "NetworkState=" + state);
}
const buffered = video.buffered;
let buffered_string = "";
for (let i = 0; i < buffered.length; i++) {
buffered_string += "(" + buffered.start(i) + "-" + buffered.end(i) + ")";
}
if (buffered_string != current_buffered_string) {
current_buffered_string = buffered_string;
events.push(prefix + "Buffered=" + buffered_string);
}
}
for (var prop in video) {
if (prop.slice(0,2) == "on") {
video[prop] = function(e) {
events.push(e.type);
pollTagState("+")
}
}
}
pollTagState("");
const interval = setInterval(function() { pollTagState("") }, 1);
video.onerror = function(e) {
events.push("Error("+video.error.message+")");
pollTagState("+")
// Wait for network state to stabilize.
setTimeout(function() {
clearInterval(interval);
resolve(events);
}, 100);
};
video.src = url;
video.play().catch(e=>0);
});
}
const nonexistant_remote = "http://localhost:8000/media/nonexistant.cgi";
const existant_remote = "http://localhost:8000/media/video-throttled-load.cgi?name=resources/test.txt&throttle=200&type=text/plain";
// First do a warmup run. Switching between sources adds some events, so
// the first run will be slightly different.
await get_all_events(nonexistant_remote);
// Get events for a nonexistant remote resource.
const nonexisting_events = await get_all_events(nonexistant_remote);
// Get events for a existant remote resource.
const existing_events = await get_all_events(existant_remote);
console.log(existing_events.join(","));
console.log(nonexisting_events.join(","));
assert_equals(existing_events.join(","), nonexisting_events.join(","));
});
</script>
...@@ -1532,14 +1532,17 @@ void HTMLMediaElement::WaitForSourceChange() { ...@@ -1532,14 +1532,17 @@ void HTMLMediaElement::WaitForSourceChange() {
GetLayoutObject()->UpdateFromElement(); GetLayoutObject()->UpdateFromElement();
} }
void HTMLMediaElement::NoneSupported(const String& message) { void HTMLMediaElement::NoneSupported(const String& input_message) {
BLINK_MEDIA_LOG << "NoneSupported(" << (void*)this << ", message='" << message BLINK_MEDIA_LOG << "NoneSupported(" << (void*)this << ", message='"
<< "')"; << input_message << "')";
StopPeriodicTimers(); StopPeriodicTimers();
load_state_ = kWaitingForSource; load_state_ = kWaitingForSource;
current_source_node_ = nullptr; current_source_node_ = nullptr;
String empty_string;
const String& message = MediaShouldBeOpaque() ? empty_string : input_message;
// 4.8.12.5 // 4.8.12.5
// The dedicated media source failure steps are the following steps: // The dedicated media source failure steps are the following steps:
...@@ -1615,11 +1618,17 @@ void HTMLMediaElement::NetworkStateChanged() { ...@@ -1615,11 +1618,17 @@ void HTMLMediaElement::NetworkStateChanged() {
} }
void HTMLMediaElement::MediaLoadingFailed(WebMediaPlayer::NetworkState error, void HTMLMediaElement::MediaLoadingFailed(WebMediaPlayer::NetworkState error,
const String& message) { const String& input_message) {
BLINK_MEDIA_LOG << "MediaLoadingFailed(" << (void*)this << ", " BLINK_MEDIA_LOG << "MediaLoadingFailed(" << (void*)this << ", "
<< static_cast<int>(error) << ", message='" << message << static_cast<int>(error) << ", message='" << input_message
<< "')"; << "')";
bool should_be_opaque = MediaShouldBeOpaque();
if (should_be_opaque)
error = WebMediaPlayer::kNetworkStateNetworkError;
String empty_string;
const String& message = should_be_opaque ? empty_string : input_message;
StopPeriodicTimers(); StopPeriodicTimers();
// If we failed while trying to load a <source> element, the movie was never // If we failed while trying to load a <source> element, the movie was never
...@@ -1722,12 +1731,14 @@ void HTMLMediaElement::SetNetworkState(WebMediaPlayer::NetworkState state) { ...@@ -1722,12 +1731,14 @@ void HTMLMediaElement::SetNetworkState(WebMediaPlayer::NetworkState state) {
void HTMLMediaElement::ChangeNetworkStateFromLoadingToIdle() { void HTMLMediaElement::ChangeNetworkStateFromLoadingToIdle() {
progress_event_timer_.Stop(); progress_event_timer_.Stop();
// Schedule one last progress event so we guarantee that at least one is fired if (!MediaShouldBeOpaque()) {
// for files that load very quickly. // Schedule one last progress event so we guarantee that at least one is
if (GetWebMediaPlayer() && GetWebMediaPlayer()->DidLoadingProgress()) // fired for files that load very quickly.
ScheduleEvent(EventTypeNames::progress); if (GetWebMediaPlayer() && GetWebMediaPlayer()->DidLoadingProgress())
ScheduleEvent(EventTypeNames::suspend); ScheduleEvent(EventTypeNames::progress);
SetNetworkState(kNetworkIdle); ScheduleEvent(EventTypeNames::suspend);
SetNetworkState(kNetworkIdle);
}
} }
void HTMLMediaElement::ReadyStateChanged() { void HTMLMediaElement::ReadyStateChanged() {
...@@ -1895,6 +1906,13 @@ void HTMLMediaElement::ProgressEventTimerFired(TimerBase*) { ...@@ -1895,6 +1906,13 @@ void HTMLMediaElement::ProgressEventTimerFired(TimerBase*) {
if (network_state_ != kNetworkLoading) if (network_state_ != kNetworkLoading)
return; return;
// If this is an cross-origin request, and we haven't discovered whether
// the media is actually playable yet, don't fire any progress events as
// those may let the page know information about the resource that it's
// not supposed to know.
if (MediaShouldBeOpaque())
return;
double time = WTF::CurrentTime(); double time = WTF::CurrentTime();
double timedelta = time - previous_progress_time_; double timedelta = time - previous_progress_time_;
...@@ -4239,6 +4257,11 @@ bool HTMLMediaElement::WasAutoplayInitiated() { ...@@ -4239,6 +4257,11 @@ bool HTMLMediaElement::WasAutoplayInitiated() {
return autoplay_policy_->WasAutoplayInitiated(); return autoplay_policy_->WasAutoplayInitiated();
} }
bool HTMLMediaElement::MediaShouldBeOpaque() const {
return !IsMediaDataCORSSameOrigin(GetDocument().GetSecurityOrigin()) &&
ready_state_ < kHaveMetadata && !FastGetAttribute(srcAttr).IsEmpty();
}
void HTMLMediaElement::CheckViewportIntersectionTimerFired(TimerBase*) { void HTMLMediaElement::CheckViewportIntersectionTimerFired(TimerBase*) {
bool should_report_root_bounds = true; bool should_report_root_bounds = true;
IntersectionGeometry geometry(nullptr, *this, Vector<Length>(), IntersectionGeometry geometry(nullptr, *this, Vector<Length>(),
......
...@@ -263,8 +263,8 @@ class CORE_EXPORT HTMLMediaElement ...@@ -263,8 +263,8 @@ class CORE_EXPORT HTMLMediaElement
using HTMLElement::GetExecutionContext; using HTMLElement::GetExecutionContext;
bool HasSingleSecurityOrigin() const { bool HasSingleSecurityOrigin() const {
return GetWebMediaPlayer() && return GetWebMediaPlayer() ? GetWebMediaPlayer()->HasSingleSecurityOrigin()
GetWebMediaPlayer()->HasSingleSecurityOrigin(); : true;
} }
bool IsFullscreen() const; bool IsFullscreen() const;
...@@ -347,6 +347,11 @@ class CORE_EXPORT HTMLMediaElement ...@@ -347,6 +347,11 @@ class CORE_EXPORT HTMLMediaElement
InsertionNotificationRequest InsertedInto(ContainerNode*) override; InsertionNotificationRequest InsertedInto(ContainerNode*) override;
void RemovedFrom(ContainerNode*) override; void RemovedFrom(ContainerNode*) override;
// Return true if media is cross origin from the current document
// and has not passed a cors check, meaning that we should return
// as little information as possible about it.
bool MediaShouldBeOpaque() const;
void DidMoveToNewDocument(Document& old_document) override; void DidMoveToNewDocument(Document& old_document) override;
virtual KURL PosterImageURL() const { return KURL(); } virtual KURL PosterImageURL() const { return KURL(); }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment