Commit 0c45ffd2 authored by Matt Falkenhagen's avatar Matt Falkenhagen Committed by Commit Bot

Disallow access to opaque CSS responses.

Bug: 848786
Change-Id: Ie53fbf644afdd76d7c65649a05c939c63d89b4ec
Reviewed-on: https://chromium-review.googlesource.com/1088335Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#565537}
parent bf42268f
This is a testharness.js-based test.
PASS setup global state
PASS MIME checking of CSS resources fetched via service worker when Content-Type is not set.
FAIL Same-origin policy for access to CSS resources fetched via service worker assert_throws: function "() => {
f.contentDocument.styleSheets[0].cssRules[0].cssText;
}" did not throw
PASS cleanup global state
Harness: the test ran to completion.
......@@ -154,6 +154,7 @@ CSSStyleSheet* CSSStyleSheet::CreateInline(Node& owner_node,
const WTF::TextEncoding& encoding) {
CSSParserContext* parser_context = CSSParserContext::Create(
owner_node.GetDocument(), owner_node.GetDocument().BaseURL(),
false /* is_opaque_response_from_service_worker */,
owner_node.GetDocument().GetReferrerPolicy(), encoding);
StyleSheetContents* sheet =
StyleSheetContents::Create(base_url.GetString(), parser_context);
......@@ -317,6 +318,12 @@ void CSSStyleSheet::ClearOwnerNode() {
bool CSSStyleSheet::CanAccessRules() const {
if (enable_rule_access_for_inspector_)
return true;
// Opaque responses should never be accessible, mod DevTools. See comments for
// IsOpaqueResponseFromServiceWorker().
if (contents_->IsOpaqueResponseFromServiceWorker())
return false;
if (is_inline_stylesheet_)
return true;
KURL base_url = contents_->BaseURL();
......
......@@ -27,9 +27,9 @@ CSSParserContext* CSSParserContext::Create(const ExecutionContext& context) {
policy_disposition = kCheckContentSecurityPolicy;
return new CSSParserContext(
context.Url(), WTF::TextEncoding(), kHTMLStandardMode, kHTMLStandardMode,
kLiveProfile, referrer, true, false, context.GetSecureContextMode(),
policy_disposition,
context.Url(), false /* is_opaque_response_from_service_worker */,
WTF::TextEncoding(), kHTMLStandardMode, kHTMLStandardMode, kLiveProfile,
referrer, true, false, context.GetSecureContextMode(), policy_disposition,
context.IsDocument() ? &ToDocument(context) : nullptr);
}
......@@ -54,8 +54,9 @@ CSSParserContext* CSSParserContext::Create(
const CSSParserContext* other,
const Document* use_counter_document) {
return new CSSParserContext(
other->base_url_, other->charset_, other->mode_, other->match_mode_,
other->profile_, other->referrer_, other->is_html_document_,
other->base_url_, other->is_opaque_response_from_service_worker_,
other->charset_, other->mode_, other->match_mode_, other->profile_,
other->referrer_, other->is_html_document_,
other->use_legacy_background_size_shorthand_behavior_,
other->secure_context_mode_, other->should_check_content_security_policy_,
use_counter_document);
......@@ -65,11 +66,13 @@ CSSParserContext* CSSParserContext::Create(
CSSParserContext* CSSParserContext::Create(
const CSSParserContext* other,
const KURL& base_url,
bool is_opaque_response_from_service_worker,
ReferrerPolicy referrer_policy,
const WTF::TextEncoding& charset,
const Document* use_counter_document) {
return new CSSParserContext(
base_url, charset, other->mode_, other->match_mode_, other->profile_,
base_url, is_opaque_response_from_service_worker, charset, other->mode_,
other->match_mode_, other->profile_,
Referrer(base_url.StrippedForUseAsReferrer(), referrer_policy),
other->is_html_document_,
other->use_legacy_background_size_shorthand_behavior_,
......@@ -83,23 +86,26 @@ CSSParserContext* CSSParserContext::Create(
SecureContextMode secure_context_mode,
SelectorProfile profile,
const Document* use_counter_document) {
return new CSSParserContext(KURL(), WTF::TextEncoding(), mode, mode, profile,
Referrer(), false, false, secure_context_mode,
kDoNotCheckContentSecurityPolicy,
use_counter_document);
return new CSSParserContext(
KURL(), false /* is_opaque_response_from_service_worker */,
WTF::TextEncoding(), mode, mode, profile, Referrer(), false, false,
secure_context_mode, kDoNotCheckContentSecurityPolicy,
use_counter_document);
}
// static
CSSParserContext* CSSParserContext::Create(const Document& document) {
return CSSParserContext::Create(document, document.BaseURL(),
document.GetReferrerPolicy(),
WTF::TextEncoding(), kLiveProfile);
return CSSParserContext::Create(
document, document.BaseURL(),
false /* is_opaque_response_from_service_worker */,
document.GetReferrerPolicy(), WTF::TextEncoding(), kLiveProfile);
}
// static
CSSParserContext* CSSParserContext::Create(
const Document& document,
const KURL& base_url_override,
bool is_opaque_response_from_service_worker,
ReferrerPolicy referrer_policy_override,
const WTF::TextEncoding& charset,
SelectorProfile profile) {
......@@ -131,13 +137,15 @@ CSSParserContext* CSSParserContext::Create(
policy_disposition = kCheckContentSecurityPolicy;
return new CSSParserContext(
base_url_override, charset, mode, match_mode, profile, referrer,
document.IsHTMLDocument(), use_legacy_background_size_shorthand_behavior,
base_url_override, is_opaque_response_from_service_worker, charset, mode,
match_mode, profile, referrer, document.IsHTMLDocument(),
use_legacy_background_size_shorthand_behavior,
document.GetSecureContextMode(), policy_disposition, &document);
}
CSSParserContext::CSSParserContext(
const KURL& base_url,
bool is_opaque_response_from_service_worker,
const WTF::TextEncoding& charset,
CSSParserMode mode,
CSSParserMode match_mode,
......@@ -149,6 +157,8 @@ CSSParserContext::CSSParserContext(
ContentSecurityPolicyDisposition policy_disposition,
const Document* use_counter_document)
: base_url_(base_url),
is_opaque_response_from_service_worker_(
is_opaque_response_from_service_worker),
charset_(charset),
mode_(mode),
match_mode_(match_mode),
......@@ -190,6 +200,10 @@ const CSSParserContext* StrictCSSParserContext(
return context;
}
bool CSSParserContext::IsOpaqueResponseFromServiceWorker() const {
return is_opaque_response_from_service_worker_;
}
bool CSSParserContext::IsSecureContext() const {
return secure_context_mode_ == SecureContextMode::kSecureContext;
}
......
......@@ -43,6 +43,7 @@ class CORE_EXPORT CSSParserContext
static CSSParserContext* Create(const CSSParserContext* other,
const KURL& base_url_override,
bool is_opaque_response_from_service_worker,
ReferrerPolicy referrer_policy_override,
const WTF::TextEncoding& charset_override,
const Document* use_counter_document);
......@@ -56,6 +57,7 @@ class CORE_EXPORT CSSParserContext
static CSSParserContext* Create(
const Document&,
const KURL& base_url_override,
bool is_opaque_response_from_service_worker,
ReferrerPolicy referrer_policy_override,
const WTF::TextEncoding& charset = WTF::TextEncoding(),
SelectorProfile = kLiveProfile);
......@@ -75,6 +77,9 @@ class CORE_EXPORT CSSParserContext
bool IsHTMLDocument() const { return is_html_document_; }
bool IsLiveProfile() const { return profile_ == kLiveProfile; }
// See documentation in StyleSheetContents for this function.
bool IsOpaqueResponseFromServiceWorker() const;
bool IsSecureContext() const;
// This quirk is to maintain compatibility with Android apps built on
......@@ -109,6 +114,7 @@ class CORE_EXPORT CSSParserContext
private:
CSSParserContext(const KURL& base_url,
bool is_opaque_response_from_service_worker,
const WTF::TextEncoding& charset,
CSSParserMode,
CSSParserMode match_mode,
......@@ -121,6 +127,7 @@ class CORE_EXPORT CSSParserContext
const Document* use_counter_document);
KURL base_url_;
const bool is_opaque_response_from_service_worker_;
WTF::TextEncoding charset_;
CSSParserMode mode_;
CSSParserMode match_mode_;
......
......@@ -534,8 +534,10 @@ SelectorQuery* SelectorQueryCache::Add(const AtomicString& selectors,
CSSSelectorList selector_list = CSSParser::ParseSelector(
CSSParserContext::Create(
document, document.BaseURL(), document.GetReferrerPolicy(),
WTF::TextEncoding(), CSSParserContext::kSnapshotProfile),
document, document.BaseURL(),
false /* is_opaque_response_from_service_worker */,
document.GetReferrerPolicy(), WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
nullptr, selectors);
if (!selector_list.First()) {
......
......@@ -68,9 +68,11 @@ TEST(SelectorQueryTest, NotMatchingPseudoElement) {
"<body><style>span::before { content: 'X' }</style><span></span></body>");
CSSSelectorList selector_list = CSSParser::ParseSelector(
CSSParserContext::Create(*document, NullURL(), kReferrerPolicyDefault,
WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
CSSParserContext::Create(
*document, NullURL(),
false /* is_opaque_response_from_service_worker */,
kReferrerPolicyDefault, WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
nullptr, "span::before");
std::unique_ptr<SelectorQuery> query =
SelectorQuery::Adopt(std::move(selector_list));
......@@ -78,9 +80,11 @@ TEST(SelectorQueryTest, NotMatchingPseudoElement) {
EXPECT_EQ(nullptr, elm);
selector_list = CSSParser::ParseSelector(
CSSParserContext::Create(*document, NullURL(), kReferrerPolicyDefault,
WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
CSSParserContext::Create(
*document, NullURL(),
false /* is_opaque_response_from_service_worker */,
kReferrerPolicyDefault, WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
nullptr, "span");
query = SelectorQuery::Adopt(std::move(selector_list));
elm = query->QueryFirst(*document);
......@@ -97,9 +101,11 @@ TEST(SelectorQueryTest, LastOfTypeNotFinishedParsing) {
document->body()->BeginParsingChildren();
CSSSelectorList selector_list = CSSParser::ParseSelector(
CSSParserContext::Create(*document, NullURL(), kReferrerPolicyDefault,
WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
CSSParserContext::Create(
*document, NullURL(),
false /* is_opaque_response_from_service_worker */,
kReferrerPolicyDefault, WTF::TextEncoding(),
CSSParserContext::kSnapshotProfile),
nullptr, "p:last-of-type");
std::unique_ptr<SelectorQuery> query =
SelectorQuery::Adopt(std::move(selector_list));
......
......@@ -78,10 +78,11 @@ void StyleRuleImport::NotifyFinished(Resource* resource) {
document = parent_style_sheet_->SingleOwnerDocument();
context = parent_style_sheet_->ParserContext();
}
context =
CSSParserContext::Create(context, cached_style_sheet->GetResponse().Url(),
cached_style_sheet->GetReferrerPolicy(),
cached_style_sheet->Encoding(), document);
context = CSSParserContext::Create(
context, cached_style_sheet->GetResponse().Url(),
cached_style_sheet->GetResponse().IsOpaqueResponseFromServiceWorker(),
cached_style_sheet->GetReferrerPolicy(), cached_style_sheet->Encoding(),
document);
style_sheet_ =
StyleSheetContents::Create(this, cached_style_sheet->Url(), context);
......
......@@ -135,12 +135,34 @@ class CORE_EXPORT StyleSheetContents
StyleRuleImport* OwnerRule() const { return owner_rule_; }
void ClearOwnerRule() { owner_rule_ = nullptr; }
// Note that href is the URL that started the redirect chain that led to
// this style sheet. This property probably isn't useful for much except
// the JavaScript binding (which needs to use this value for security).
// The URL that started the redirect chain that led to this
// style sheet. This property probably isn't useful for much except the
// JavaScript binding (which needs to use this value for security).
String OriginalURL() const { return original_url_; }
// The final request URL after redirects. WARNING: Be careful when
// using this for security checks. It can be different from the actual
// response URL if a service worker is involved. See
// IsOpaqueResponseFromServiceWorker().
const KURL& BaseURL() const { return parser_context_->BaseURL(); }
// True if a service worker intercepted the request for this style sheet and
// returned an opaque response. This context should NOT have access to the
// contents, regardless of BaseURL().
//
// For example:
// 1. Page at a.com requests a.com/style.css.
// 2. Service worker responds with b.com/style.css (without CORS).
// 3. The BaseURL() is "a.com/style.css" but this context is should not have
// access to contents.
//
// You might ask why we don't change BaseURL() to be the actual response URL.
// In fact, the spec says we should! See crbug.com/553535. But we would still
// need this "is opaque" bit, since in step 2 above the service worker might
// have used CORS to get a non-opaque response from b.com.
bool IsOpaqueResponseFromServiceWorker() const {
return parser_context_->IsOpaqueResponseFromServiceWorker();
}
unsigned RuleCount() const;
StyleRuleBase* RuleAt(unsigned index) const;
......
......@@ -202,6 +202,7 @@ void ProcessingInstruction::NotifyFinished(Resource* resource) {
CSSStyleSheetResource* style_resource = ToCSSStyleSheetResource(resource);
CSSParserContext* parser_context = CSSParserContext::Create(
GetDocument(), style_resource->GetResponse().Url(),
style_resource->GetResponse().IsOpaqueResponseFromServiceWorker(),
style_resource->GetReferrerPolicy(), style_resource->Encoding());
StyleSheetContents* new_sheet =
......
......@@ -92,6 +92,7 @@ void LinkStyle::NotifyFinished(Resource* resource) {
CSSParserContext* parser_context = CSSParserContext::Create(
GetDocument(), cached_style_sheet->GetResponse().Url(),
cached_style_sheet->GetResponse().IsOpaqueResponseFromServiceWorker(),
cached_style_sheet->GetReferrerPolicy(), cached_style_sheet->Encoding());
if (StyleSheetContents* parsed_sheet =
......
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