Bug 1584993: Make CSP frame-ancestors work with fission enabled. r=jkt,farre,valentin

Differential Revision: https://phabricator.services.mozilla.com/D49147

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Christoph Kerschbaumer 2019-10-22 10:57:43 +00:00
Родитель 9549d17872
Коммит 61c17da3e9
14 изменённых файлов: 333 добавлений и 89 удалений

Просмотреть файл

@ -3286,21 +3286,6 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
SetPrincipals(principal, principal);
}
// ----- Enforce frame-ancestor policy on any applied policies
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
if (docShell) {
bool safeAncestry = false;
// PermitsAncestry sends violation reports when necessary
rv = mCSP->PermitsAncestry(docShell, &safeAncestry);
if (NS_FAILED(rv) || !safeAncestry) {
MOZ_LOG(gCspPRLog, LogLevel::Debug,
("CSP doesn't like frame's ancestry, not loading."));
// stop! ERROR page!
aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
}
}
ApplySettingsFromCSP(false);
return NS_OK;
}

Просмотреть файл

@ -6,8 +6,8 @@
#include "nsIContentPolicy.idl"
interface nsIURI;
interface nsIDocShell;
interface nsIEventTarget;
interface nsILoadInfo;
interface nsIPrincipal;
interface nsICSPEventListener;
@ -275,14 +275,14 @@ interface nsIContentSecurityPolicy : nsISerializable
* NOTE: Calls to this may trigger violation reports when queried, so this
* value should not be cached.
*
* @param docShell
* containing the protected resource
* @param aLoadInfo
* The loadinfo of the channel containing the protected resource
* @return
* true if the frame's ancestors are all allowed by policy (except for
* report-only policies, which will send reports and then return true
* here when violated).
*/
boolean permitsAncestry(in nsIDocShell docShell);
boolean permitsAncestry(in nsILoadInfo aLoadInfo);
/**

Просмотреть файл

@ -0,0 +1,202 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DOMSecurityManager.h"
#include "nsCSPContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsIMultiPartChannel.h"
#include "nsIObserverService.h"
#include "nsIHttpProtocolHandler.h"
using namespace mozilla;
namespace {
StaticRefPtr<DOMSecurityManager> gDOMSecurityManager;
} // namespace
static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
nsIHttpChannel** aHttpChannel) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
if (httpChannel) {
httpChannel.forget(aHttpChannel);
return NS_OK;
}
nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
if (!multipart) {
*aHttpChannel = nullptr;
return NS_OK;
}
nsCOMPtr<nsIChannel> baseChannel;
nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
httpChannel = do_QueryInterface(baseChannel);
httpChannel.forget(aHttpChannel);
return NS_OK;
}
NS_INTERFACE_MAP_BEGIN(DOMSecurityManager)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(DOMSecurityManager)
NS_IMPL_RELEASE(DOMSecurityManager)
/* static */
void DOMSecurityManager::Initialize() {
MOZ_ASSERT(!gDOMSecurityManager);
MOZ_ASSERT(NS_IsMainThread());
if (!XRE_IsParentProcess()) {
return;
}
RefPtr<DOMSecurityManager> service = new DOMSecurityManager();
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
return;
}
obs->AddObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC, false);
obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
gDOMSecurityManager = service.forget();
}
/* static */
void DOMSecurityManager::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
if (!gDOMSecurityManager) {
return;
}
RefPtr<DOMSecurityManager> service = gDOMSecurityManager.forget();
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
return;
}
obs->RemoveObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
obs->RemoveObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
NS_IMETHODIMP
DOMSecurityManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
Shutdown();
return NS_OK;
}
MOZ_ASSERT(!strcmp(aTopic, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC));
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aSubject);
if (NS_WARN_IF(!channel)) {
return NS_OK;
}
nsresult rv = ParseCSPAndEnforceFrameAncestorCheck(channel);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
nsresult DOMSecurityManager::ParseCSPAndEnforceFrameAncestorCheck(
nsIChannel* aChannel) {
MOZ_ASSERT(aChannel);
// CSP can only hang off an http channel, if this channel is not
// an http channel then there is nothing to do here.
nsCOMPtr<nsIHttpChannel> httpChannel;
nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!httpChannel) {
return NS_OK;
}
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
nsContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
// frame-ancestor check only makes sense for subdocument loads, if this is
// not a load of such type, there is nothing to do here.
if (contentType != nsIContentPolicy::TYPE_SUBDOCUMENT) {
return NS_OK;
}
nsAutoCString tCspHeaderValue, tCspROHeaderValue;
Unused << httpChannel->GetResponseHeader(
NS_LITERAL_CSTRING("content-security-policy"), tCspHeaderValue);
Unused << httpChannel->GetResponseHeader(
NS_LITERAL_CSTRING("content-security-policy-report-only"),
tCspROHeaderValue);
// if there are no CSP values, then there is nothing to do here.
if (tCspHeaderValue.IsEmpty() && tCspROHeaderValue.IsEmpty()) {
return NS_OK;
}
NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
RefPtr<nsCSPContext> csp = new nsCSPContext();
nsCOMPtr<nsIPrincipal> resultPrincipal;
rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
aChannel, getter_AddRefs(resultPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> selfURI;
aChannel->GetURI(getter_AddRefs(selfURI));
nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
nsAutoString referrerSpec;
referrerInfo->GetComputedReferrerSpec(referrerSpec);
uint64_t innerWindowID = loadInfo->GetInnerWindowID();
rv = csp->SetRequestContextWithPrincipal(resultPrincipal, selfURI,
referrerSpec, innerWindowID);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// ----- if there's a full-strength CSP header, apply it.
if (!cspHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- if there's a report-only CSP header, apply it.
if (!cspROHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- Enforce frame-ancestor policy on any applied policies
bool safeAncestry = false;
// PermitsAncestry sends violation reports when necessary
rv = csp->PermitsAncestry(loadInfo, &safeAncestry);
if (NS_FAILED(rv) || !safeAncestry) {
// stop! ERROR page!
aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
}
return NS_OK;
}

Просмотреть файл

@ -0,0 +1,31 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_DOMSecurityManager_h
#define mozilla_dom_DOMSecurityManager_h
#include "nsIObserver.h"
class DOMSecurityManager final : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
static void Initialize();
private:
DOMSecurityManager() = default;
~DOMSecurityManager() = default;
// Only enforces the frame-anecstor check which needs to happen in
// the parent because we can only access the window global in the
// parent. The actual CSP gets parsed and applied in content.
nsresult ParseCSPAndEnforceFrameAncestorCheck(nsIChannel* aChannel);
static void Shutdown();
};
#endif /* mozilla_dom_DOMSecurityManager_h */

Просмотреть файл

@ -13,6 +13,7 @@ DIRS += [ 'featurepolicy' ]
EXPORTS.mozilla.dom += [
'CSPEvalChecker.h',
'DOMSecurityManager.h',
'FramingChecker.h',
'nsContentSecurityManager.h',
'nsContentSecurityUtils.h',
@ -36,6 +37,7 @@ EXPORTS += [
UNIFIED_SOURCES += [
'CSPEvalChecker.cpp',
'DOMSecurityManager.cpp',
'FramingChecker.cpp',
'nsContentSecurityManager.cpp',
'nsContentSecurityUtils.cpp',

Просмотреть файл

@ -16,8 +16,6 @@
#include "nsError.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIClassInfoImpl.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "mozilla/dom/Document.h"
#include "nsIHttpChannel.h"
#include "nsIInterfaceRequestor.h"
@ -1522,79 +1520,52 @@ nsresult nsCSPContext::AsyncReportViolation(
}
/**
* Based on the given docshell, determines if this CSP context allows the
* Based on the given loadinfo, determines if this CSP context allows the
* ancestry.
*
* In order to determine the URI of the parent document (one causing the load
* of this protected document), this function obtains the docShellTreeItem,
* then walks up the hierarchy until it finds a privileged (chrome) tree item.
* Getting the a tree item's URI looks like this in pseudocode:
*
* nsIDocShellTreeItem->GetDocument()->GetDocumentURI();
*
* aDocShell is the docShell for the protected document.
* of this protected document), this function traverses all Browsing Contexts
* until it reaches the top level browsing context.
*/
NS_IMETHODIMP
nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell,
nsCSPContext::PermitsAncestry(nsILoadInfo* aLoadInfo,
bool* outPermitsAncestry) {
nsresult rv;
MOZ_ASSERT(XRE_IsParentProcess(), "frame-ancestor check only in parent");
// Can't check ancestry without a docShell.
if (aDocShell == nullptr) {
return NS_ERROR_FAILURE;
}
nsresult rv;
*outPermitsAncestry = true;
RefPtr<mozilla::dom::BrowsingContext> ctx;
aLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
// extract the ancestry as an array
nsCOMArray<nsIURI> ancestorsArray;
nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
nsCOMPtr<nsIURI> currentURI;
nsCOMPtr<nsIURI> uriClone;
// iterate through each docShell parent item
while (NS_SUCCEEDED(
treeItem->GetInProcessParent(getter_AddRefs(parentTreeItem))) &&
parentTreeItem != nullptr) {
// stop when reaching chrome
if (parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome) {
break;
}
while (ctx) {
WindowGlobalParent* window = ctx->Canonical()->GetCurrentWindowGlobal();
if (window) {
nsCOMPtr<nsIURI> currentURI = window->GetDocumentURI();
if (currentURI) {
nsAutoCString spec;
currentURI->GetSpec(spec);
// delete the userpass from the URI.
rv = NS_MutateURI(currentURI)
.SetRef(EmptyCString())
.SetUserPass(EmptyCString())
.Finalize(uriClone);
Document* doc = parentTreeItem->GetDocument();
NS_ASSERTION(doc,
"Could not get Document from nsIDocShellTreeItem in "
"nsCSPContext::PermitsAncestry");
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
currentURI = doc->GetDocumentURI();
if (currentURI) {
// delete the userpass from the URI.
rv = NS_MutateURI(currentURI)
.SetRef(EmptyCString())
.SetUserPass(EmptyCString())
.Finalize(uriClone);
// If setUserPass fails for some reason, just return a clone of the
// current URI
if (NS_FAILED(rv)) {
rv = NS_GetURIWithoutRef(currentURI, getter_AddRefs(uriClone));
NS_ENSURE_SUCCESS(rv, rv);
// If setUserPass fails for some reason, just return a clone of the
// current URI
if (NS_FAILED(rv)) {
rv = NS_GetURIWithoutRef(currentURI, getter_AddRefs(uriClone));
NS_ENSURE_SUCCESS(rv, rv);
}
ancestorsArray.AppendElement(uriClone);
}
if (CSPCONTEXTLOGENABLED()) {
CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s",
uriClone->GetSpecOrDefault().get()));
}
ancestorsArray.AppendElement(uriClone);
}
// next ancestor
treeItem = parentTreeItem;
ctx = ctx->GetParent();
}
nsAutoString violatedDirective;

Просмотреть файл

@ -257,9 +257,7 @@ skip-if = !debug
[test_evalscript_blocked_by_strict_dynamic.html]
[test_evalscript_allowed_by_strict_dynamic.html]
[test_frameancestors.html]
skip-if = fission
[test_frameancestors_userpass.html]
skip-if = fission
[test_inlinescript.html]
[test_inlinestyle.html]
[test_invalid_source_expression.html]

Просмотреть файл

@ -51,10 +51,14 @@ var framesThatShouldLoad = {
// Number of tests that pass for this file should be 12 (8 violations 4 loads)
var expectedViolationsLeft = 8;
// CSP frame-ancestor checks happen in the parent, hence we have to
// proxy the csp violation notifications.
SpecialPowers.registerObservers("csp-on-violate-policy");
// This is used to watch the blocked data bounce off CSP and allowed data
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy");
SpecialPowers.addObserver(this, "specialpowers-csp-on-violate-policy");
}
examiner.prototype = {
observe(subject, topic, data) {
@ -81,7 +85,7 @@ examiner.prototype = {
}
if (topic === "csp-on-violate-policy") {
if (topic === "specialpowers-csp-on-violate-policy") {
//these were blocked... record that they were blocked
window.frameBlocked(asciiSpec, data);
}
@ -90,7 +94,7 @@ examiner.prototype = {
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "specialpowers-csp-on-violate-policy");
}
}

Просмотреть файл

@ -22,10 +22,14 @@ var framesThatShouldLoad = {
// Number of tests that pass for this file should be 1
var expectedViolationsLeft = 1;
// CSP frame-ancestor checks happen in the parent, hence we have to
// proxy the csp violation notifications.
SpecialPowers.registerObservers("csp-on-violate-policy");
// This is used to watch the blocked data bounce off CSP and allowed data
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy");
SpecialPowers.addObserver(this, "specialpowers-csp-on-violate-policy");
}
examiner.prototype = {
observe(subject, topic, data) {
@ -51,8 +55,7 @@ examiner.prototype = {
// was not an nsIURI, so it was probably a cross-origin report.
}
if (topic === "csp-on-violate-policy") {
if (topic === "specialpowers-csp-on-violate-policy") {
//these were blocked... record that they were blocked
window.frameBlocked(asciiSpec, data);
}
@ -61,7 +64,7 @@ examiner.prototype = {
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "specialpowers-csp-on-violate-policy");
}
}

Просмотреть файл

@ -21,13 +21,23 @@ var stringBundleService = SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
.getService(SpecialPowers.Ci.nsIStringBundleService);
var localizer = stringBundleService.createBundle("chrome://global/locale/security/csp.properties");
var warningMsg = localizer.formatStringFromName("reportURInotInReportOnlyHeader", [window.location.origin]);
function cleanup() {
SpecialPowers.postConsoleSentinel();
SimpleTest.finish();
}
// Since Bug 1584993 we parse the CSP in the parent too, hence the
// same error message appears twice in the console.
var recordConsoleMsgOnce = false;
SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
if (aMsg.message.indexOf(warningMsg) > -1) {
if (recordConsoleMsgOnce) {
return;
}
recordConsoleMsgOnce = true;
ok(true, "report-uri not specified in Report-Only should throw a CSP warning.");
SimpleTest.executeSoon(cleanup);
return;

Просмотреть файл

@ -109,6 +109,7 @@
#include "DecoderDoctorLogger.h"
#include "MediaDecoder.h"
#include "mozilla/ClearSiteData.h"
#include "mozilla/dom/DOMSecurityManager.h"
#include "mozilla/EditorController.h"
#include "mozilla/Fuzzyfox.h"
#include "mozilla/HTMLEditorController.h"
@ -308,6 +309,8 @@ nsresult nsLayoutStatics::Initialize() {
ClearSiteData::Initialize();
DOMSecurityManager::Initialize();
// Reporting API.
ReportingHeader::Initialize();

Просмотреть файл

@ -2255,6 +2255,25 @@ SpecialPowersChild.prototype._proxiedObservers = {
"specialpowers-service-worker-shutdown": function(aMessage) {
Services.obs.notifyObservers(null, "specialpowers-service-worker-shutdown");
},
"specialpowers-csp-on-violate-policy": function(aMessage) {
let subject = null;
try {
subject = Services.io.newURI(aMessage.data.subject);
} catch (ex) {
// if it's not a valid URI it must be an nsISupportsCString
subject = Cc["@mozilla.org/supports-cstring;1"].createInstance(
Ci.nsISupportsCString
);
subject.data = aMessage.data.subject;
}
Services.obs.notifyObservers(
subject,
"specialpowers-csp-on-violate-policy",
aMessage.data.data
);
},
};
SpecialPowersChild.prototype.permissionObserverProxy = {

Просмотреть файл

@ -160,7 +160,24 @@ class SpecialPowersParent extends JSWindowActorParent {
},
type: permission.type,
};
// fall through
this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
return;
case "csp-on-violate-policy":
// the subject is either an nsIURI or an nsISupportsCString
let subject = null;
if (aSubject instanceof Ci.nsIURI) {
subject = aSubject.asciiSpec;
} else if (aSubject instanceof Ci.nsISupportsCString) {
subject = aSubject.data;
} else {
throw new Error("Subject must be nsIURI or nsISupportsCString");
}
msg = {
subject,
data: aData,
};
this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
return;
default:
this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
}

Просмотреть файл

@ -1,5 +1,4 @@
[frame-ancestors-from-serviceworker.https.html]
expected: TIMEOUT
[A 'frame-ancestors' CSP directive set from a serviceworker response with a value 'none' should block rendering.]
expected: TIMEOUT
expected: FAIL