diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-04-19 13:30:44 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-04-20 08:08:53 +0000 |
commit | e9ec9ada1c390d2f0bf25e993d072c4ecf36c83a (patch) | |
tree | 57a15d6a99019d00653e99d635d2b870d21c1266 | |
parent | 4ec3a984093ea1a897607e0e5e13c4970f5bf150 (diff) |
[Backport] Enable <video> to see if a response from a service worker was cross-origin.
Previously, if the <video> src was same-origin as the page, and the
service worker intercepted the request and responded with an opaque
response (by making a cross-origin request without CORS sharing),
the <video> would be considered same-origin data.
The solution is to teach the data backing <video> about FetchResponseType,
which tells us if a response is opaque or non-opaque.
There is some complexity with multiple responses due to range requests/
partial content responses. It's possible that some responses are
opaque and some are non-opaque. Once the opaque bit is set, it shouldn't
be cleared.
This fixes tests added in:
https://chromium-review.googlesource.com/c/chromium/src/+/892683 and
https://chromium-review.googlesource.com/c/chromium/src/+/897165
Bug: 780435
Reviewed-on: https://chromium-review.googlesource.com/828564
(CVE-2018-6093)
Change-Id: I6a2aba935155dfb897fb55ba0c1d09fb251076bc
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
16 files changed, 106 insertions, 14 deletions
diff --git a/chromium/content/renderer/media/webmediaplayer_ms.cc b/chromium/content/renderer/media/webmediaplayer_ms.cc index b528aa53ec0..4a6b55c414a 100644 --- a/chromium/content/renderer/media/webmediaplayer_ms.cc +++ b/chromium/content/renderer/media/webmediaplayer_ms.cc @@ -642,6 +642,11 @@ void WebMediaPlayerMS::Paint(blink::WebCanvas* canvas, context_3d); } +bool WebMediaPlayerMS::DidGetOpaqueResponseFromServiceWorker() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return false; +} + bool WebMediaPlayerMS::HasSingleSecurityOrigin() const { DCHECK(thread_checker_.CalledOnValidThread()); return true; diff --git a/chromium/content/renderer/media/webmediaplayer_ms.h b/chromium/content/renderer/media/webmediaplayer_ms.h index ef572374903..025c4bc5abe 100644 --- a/chromium/content/renderer/media/webmediaplayer_ms.h +++ b/chromium/content/renderer/media/webmediaplayer_ms.h @@ -138,6 +138,7 @@ class CONTENT_EXPORT WebMediaPlayerMS blink::WebString GetErrorMessage() const override; bool DidLoadingProgress() override; + bool DidGetOpaqueResponseFromServiceWorker() const override; bool HasSingleSecurityOrigin() const override; bool DidPassCORSAccessCheck() const override; diff --git a/chromium/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc b/chromium/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc index 4e28d7cacf3..2f32d3c6c6a 100644 --- a/chromium/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc +++ b/chromium/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc @@ -63,6 +63,7 @@ class MockWebMediaPlayer : public blink::WebMediaPlayer, } bool DidLoadingProgress() override { return true; } + bool DidGetOpaqueResponseFromServiceWorker() const override { return false; } bool HasSingleSecurityOrigin() const override { return true; } bool DidPassCORSAccessCheck() const override { return true; } double MediaTimeForTimeValue(double timeValue) const override { return 0.0; } diff --git a/chromium/media/blink/DEPS b/chromium/media/blink/DEPS index 090921e2556..35607b195a7 100644 --- a/chromium/media/blink/DEPS +++ b/chromium/media/blink/DEPS @@ -13,6 +13,7 @@ include_rules = [ "+mojo/public/cpp/bindings", "+net/base", "+net/http", + "+services/network/public/interfaces/fetch_api.mojom.h", "+services/service_manager/public/cpp", "+third_party/WebKit/public/platform", "+third_party/WebKit/public/web", diff --git a/chromium/media/blink/multibuffer_data_source.cc b/chromium/media/blink/multibuffer_data_source.cc index 45f81d88f7c..20d479d4b2f 100644 --- a/chromium/media/blink/multibuffer_data_source.cc +++ b/chromium/media/blink/multibuffer_data_source.cc @@ -278,7 +278,8 @@ bool MultibufferDataSource::HasSingleOrigin() { bool MultibufferDataSource::DidPassCORSAccessCheck() const { if (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) return false; - // If init_cb is set, we initialization is not finished yet. + + // If init_cb is set, we know initialization is not finished yet. if (!init_cb_.is_null()) return false; if (failed_) @@ -286,6 +287,13 @@ bool MultibufferDataSource::DidPassCORSAccessCheck() const { return true; } +bool MultibufferDataSource::DidGetOpaqueResponseViaServiceWorker() const { + return url_data_->has_opaque_data(); + + // TODO(falken): Do we need to do something about |init_cb_| like + // in DidPassCORSAccessCheck()? +} + void MultibufferDataSource::MediaPlaybackRateChanged(double playback_rate) { DCHECK(render_task_runner_->BelongsToCurrentThread()); diff --git a/chromium/media/blink/multibuffer_data_source.h b/chromium/media/blink/multibuffer_data_source.h index 43695ab08ec..43d1140c141 100644 --- a/chromium/media/blink/multibuffer_data_source.h +++ b/chromium/media/blink/multibuffer_data_source.h @@ -82,6 +82,10 @@ class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource { // Returns true if the media resource passed a CORS access control check. bool DidPassCORSAccessCheck() const; + // Returns true if a service worker provided the media resource response, + // and the response was opaque. + bool DidGetOpaqueResponseViaServiceWorker() const; + // Notifies changes in playback state for controlling media buffering // behavior. void MediaPlaybackRateChanged(double playback_rate); diff --git a/chromium/media/blink/resource_multibuffer_data_provider.cc b/chromium/media/blink/resource_multibuffer_data_provider.cc index de6d590757d..db9c7fe763c 100644 --- a/chromium/media/blink/resource_multibuffer_data_provider.cc +++ b/chromium/media/blink/resource_multibuffer_data_provider.cc @@ -35,6 +35,25 @@ using blink::WebURLResponse; namespace media { +namespace { + +bool IsOpaqueData(network::mojom::FetchResponseType response_type) { + switch (response_type) { + case network::mojom::FetchResponseType::kBasic: + case network::mojom::FetchResponseType::kCORS: + case network::mojom::FetchResponseType::kDefault: + return false; + case network::mojom::FetchResponseType::kError: + case network::mojom::FetchResponseType::kOpaque: + case network::mojom::FetchResponseType::kOpaqueRedirect: + return true; + } + NOTREACHED(); + return false; +} + +} // namespace + // The number of milliseconds to wait before retrying a failed load. const int kLoaderFailedRetryDelayMs = 250; @@ -323,6 +342,11 @@ void ResourceMultiBufferDataProvider::DidReceiveResponse( url_data_->url_index()->TryInsert(destination_url_data); } + // This is vital for security! A service worker can respond with a response + // from a different origin, so this response type is needed to detect that. + destination_url_data->set_has_opaque_data( + IsOpaqueData(response.ResponseTypeViaServiceWorker())); + if (destination_url_data != url_data_) { // At this point, we've encountered a redirect, or found a better url data // instance for the data that we're about to download. diff --git a/chromium/media/blink/url_index.cc b/chromium/media/blink/url_index.cc index 99291258eb3..84c5aafa10b 100644 --- a/chromium/media/blink/url_index.cc +++ b/chromium/media/blink/url_index.cc @@ -51,6 +51,7 @@ UrlData::UrlData(const GURL& url, CORSMode cors_mode, UrlIndex* url_index) length_(kPositionNotSpecified), range_supported_(false), cacheable_(false), + has_opaque_data_(false), last_used_(), multibuffer_(this, url_index_->block_shift_) {} @@ -86,6 +87,9 @@ void UrlData::MergeFrom(const scoped_refptr<UrlData>& other) { last_modified_ = other->last_modified_; } bytes_read_from_cache_ += other->bytes_read_from_cache_; + // set_has_opaque_data() will not relax from opaque to non-opaque if already + // opaque. + set_has_opaque_data(other->has_opaque_data_); multibuffer()->MergeFrom(other->multibuffer()); } } @@ -102,6 +106,12 @@ void UrlData::set_length(int64_t length) { } } +void UrlData::set_has_opaque_data(bool has_opaque_data) { + if (has_opaque_data_) + return; + has_opaque_data_ = has_opaque_data; +} + void UrlData::RedirectTo(const scoped_refptr<UrlData>& url_data) { DCHECK(thread_checker_.CalledOnValidThread()); // Copy any cached data over to the new location. diff --git a/chromium/media/blink/url_index.h b/chromium/media/blink/url_index.h index 165b6187f7b..6ffa80800dc 100644 --- a/chromium/media/blink/url_index.h +++ b/chromium/media/blink/url_index.h @@ -19,6 +19,7 @@ #include "media/blink/lru.h" #include "media/blink/media_blink_export.h" #include "media/blink/multibuffer.h" +#include "services/network/public/interfaces/fetch_api.mojom.h" #include "url/gurl.h" namespace media { @@ -98,6 +99,8 @@ class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> { // Returns our url_index. UrlIndex* url_index() const { return url_index_; } + bool has_opaque_data() const { return has_opaque_data_; } + // Notifies the url index that this is currently used. // The url <-> URLData mapping will be eventually be invalidated if // this is not called regularly. @@ -106,7 +109,7 @@ class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> { // Call this before we add some data to the multibuffer(). // If the multibuffer is empty, the data origin is set from // |origin| and returns true. If not, it compares |origin| - // to the previous origin and returns wheather they match or not. + // to the previous origin and returns whether they match or not. bool ValidateDataOrigin(const GURL& origin); // Setters. @@ -116,6 +119,7 @@ class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> { void set_range_supported(); void set_last_modified(base::Time last_modified); void set_etag(const std::string& etag); + void set_has_opaque_data(bool has_opaque_data); // A redirect has occured (or we've found a better UrlData for the same // resource). @@ -187,6 +191,10 @@ class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> { // will not cache this url. bool cacheable_; + // True if a service worker intercepted a request for this resource + // and provided an opaque response. + bool has_opaque_data_; + // Last time some media time used this resource. // Note that we use base::Time rather than base::TimeTicks because // TimeTicks will stop advancing when a machine goes to sleep. diff --git a/chromium/media/blink/webmediaplayer_impl.cc b/chromium/media/blink/webmediaplayer_impl.cc index 8b681abb3dc..8628760d4d0 100644 --- a/chromium/media/blink/webmediaplayer_impl.cc +++ b/chromium/media/blink/webmediaplayer_impl.cc @@ -1103,6 +1103,12 @@ void WebMediaPlayerImpl::Paint(blink::WebCanvas* canvas, pipeline_metadata_.video_decoder_config.video_rotation(), context_3d); } +bool WebMediaPlayerImpl::DidGetOpaqueResponseFromServiceWorker() const { + if (data_source_) + return data_source_->DidGetOpaqueResponseViaServiceWorker(); + return false; +} + bool WebMediaPlayerImpl::HasSingleSecurityOrigin() const { if (data_source_) return data_source_->HasSingleOrigin(); diff --git a/chromium/media/blink/webmediaplayer_impl.h b/chromium/media/blink/webmediaplayer_impl.h index a89c9fb6730..a107e2d3d87 100644 --- a/chromium/media/blink/webmediaplayer_impl.h +++ b/chromium/media/blink/webmediaplayer_impl.h @@ -180,6 +180,7 @@ class MEDIA_BLINK_EXPORT WebMediaPlayerImpl blink::WebString GetErrorMessage() const override; bool DidLoadingProgress() override; + bool DidGetOpaqueResponseFromServiceWorker() const override; bool HasSingleSecurityOrigin() const override; bool DidPassCORSAccessCheck() const override; diff --git a/chromium/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp b/chromium/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp index 86144b4c736..c05d0a33521 100644 --- a/chromium/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp +++ b/chromium/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp @@ -1468,17 +1468,30 @@ bool HTMLMediaElement::IsSafeToLoadURL(const KURL& url, bool HTMLMediaElement::IsMediaDataCORSSameOrigin( const SecurityOrigin* origin) const { - // hasSingleSecurityOrigin() tells us whether the origin in the src is - // the same as the actual request (i.e. after redirect). - // didPassCORSAccessCheck() means it was a successful CORS-enabled fetch - // (vs. non-CORS-enabled or failed). - // taintsCanvas() does checkAccess() on the URL plus allow data sources, - // to ensure that it is not a URL that requires CORS (basically same - // origin). - return HasSingleSecurityOrigin() && - ((GetWebMediaPlayer() && - GetWebMediaPlayer()->DidPassCORSAccessCheck()) || - !origin->TaintsCanvas(currentSrc())); + // If a service worker handled the request, we don't know if the origin in the + // src is the same as the actual response URL so can't rely on URL checks + // alone. So detect an opaque response via + // DidGetOpaqueResponseFromServiceWorker(). + if (GetWebMediaPlayer() && + GetWebMediaPlayer()->DidGetOpaqueResponseFromServiceWorker()) { + return false; + } + + // At this point, either a service worker was not used, or it didn't provide + // an opaque response, so continue with the normal checks. + + // HasSingleSecurityOrigin() tells us whether the origin in the src + // is the same as the actual request (i.e. after redirects). + if (!HasSingleSecurityOrigin()) + return false; + + // DidPassCORSAccessCheck() means it was a successful CORS-enabled fetch (vs. + // non-CORS-enabled or failed). TaintsCanvas() does CheckAccess() on the URL + // plus allows data sources, to ensure that it is not a URL that requires + // CORS (basically same origin). + return (GetWebMediaPlayer() && + GetWebMediaPlayer()->DidPassCORSAccessCheck()) || + !origin->TaintsCanvas(currentSrc()); } bool HTMLMediaElement::IsInCrossOriginFrame() const { diff --git a/chromium/third_party/WebKit/Source/platform/exported/WebURLResponse.cpp b/chromium/third_party/WebKit/Source/platform/exported/WebURLResponse.cpp index 4e3248a3bd2..02e1622701a 100644 --- a/chromium/third_party/WebKit/Source/platform/exported/WebURLResponse.cpp +++ b/chromium/third_party/WebKit/Source/platform/exported/WebURLResponse.cpp @@ -318,6 +318,11 @@ void WebURLResponse::SetResponseTypeViaServiceWorker( resource_response_->SetResponseTypeViaServiceWorker(value); } +network::mojom::FetchResponseType WebURLResponse::ResponseTypeViaServiceWorker() + const { + return resource_response_->ResponseTypeViaServiceWorker(); +} + void WebURLResponse::SetURLListViaServiceWorker( const WebVector<WebURL>& url_list_via_service_worker) { Vector<KURL> url_list(url_list_via_service_worker.size()); diff --git a/chromium/third_party/WebKit/Source/platform/testing/EmptyWebMediaPlayer.h b/chromium/third_party/WebKit/Source/platform/testing/EmptyWebMediaPlayer.h index 2e4eaaab79e..9956dae8750 100644 --- a/chromium/third_party/WebKit/Source/platform/testing/EmptyWebMediaPlayer.h +++ b/chromium/third_party/WebKit/Source/platform/testing/EmptyWebMediaPlayer.h @@ -39,6 +39,7 @@ class EmptyWebMediaPlayer : public WebMediaPlayer { ReadyState GetReadyState() const override { return kReadyStateHaveNothing; } WebString GetErrorMessage() const override; bool DidLoadingProgress() override { return false; } + bool DidGetOpaqueResponseFromServiceWorker() const override { return false; } bool HasSingleSecurityOrigin() const override { return true; } bool DidPassCORSAccessCheck() const override { return true; } double MediaTimeForTimeValue(double time_value) const override { diff --git a/chromium/third_party/WebKit/public/platform/WebMediaPlayer.h b/chromium/third_party/WebKit/public/platform/WebMediaPlayer.h index c2c2967b3ab..ef20586bd98 100644 --- a/chromium/third_party/WebKit/public/platform/WebMediaPlayer.h +++ b/chromium/third_party/WebKit/public/platform/WebMediaPlayer.h @@ -176,6 +176,7 @@ class WebMediaPlayer { virtual bool DidLoadingProgress() = 0; + virtual bool DidGetOpaqueResponseFromServiceWorker() const = 0; virtual bool HasSingleSecurityOrigin() const = 0; virtual bool DidPassCORSAccessCheck() const = 0; diff --git a/chromium/third_party/WebKit/public/platform/WebURLResponse.h b/chromium/third_party/WebKit/public/platform/WebURLResponse.h index 11621be2034..6689ade5768 100644 --- a/chromium/third_party/WebKit/public/platform/WebURLResponse.h +++ b/chromium/third_party/WebKit/public/platform/WebURLResponse.h @@ -225,9 +225,12 @@ class WebURLResponse { // details. BLINK_PLATFORM_EXPORT void SetWasFallbackRequiredByServiceWorker(bool); - // The type of the response which was returned by the ServiceWorker. + // The type of the response, if it was returned by a service worker. This is + // kDefault if the response was not returned by a service worker. BLINK_PLATFORM_EXPORT void SetResponseTypeViaServiceWorker( network::mojom::FetchResponseType); + BLINK_PLATFORM_EXPORT network::mojom::FetchResponseType + ResponseTypeViaServiceWorker() const; // The URL list of the Response object the ServiceWorker passed to // respondWith(). See ServiceWorkerResponseInfo::url_list_via_service_worker() |