Bug 1589275 - Run DocumentChannel CSP checks in the parent, and send only the violations to the content process. r=nika,ckerschb

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Matt Woodrow 2020-04-01 22:10:21 +00:00
parent 70bbf6da2a
commit c7b55e1d51
7 changed files with 141 additions and 64 deletions

View File

@ -8,6 +8,7 @@
#define mozilla_net_ADocumentChannelBridge_h
#include "mozilla/net/PDocumentChannelParent.h"
#include "mozilla/dom/nsCSPContext.h"
namespace mozilla {
namespace net {
@ -37,11 +38,17 @@ class ADocumentChannelBridge {
// Delete the bridge, and drop any refs to the DocumentLoadListener
virtual void Delete() = 0;
// Checks if we should allow a redirect to aNewURI.
// We need this because we currently can only do CSP checks in the content
// process in order to get the right events fired.
virtual RefPtr<PDocumentChannelParent::ConfirmRedirectPromise>
ConfirmRedirect(const LoadInfoArgs& aLoadInfo, nsIURI* aNewURI) = 0;
// Report a CSP violation event in the originating process, using
// nsCSPContext::AsyncReportViolation.
// aIsCspToInherit is true if aContext is the CSP to inherit (from
// the nsDocShellLoadState), which is used to determine the right
// loading Document when deserializing aContext. This should no longer be
// necessary after bug 1625366.
virtual void CSPViolation(
nsCSPContext* aContext, bool aIsCspToInherit, nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex, const nsAString& aObserverSubject) = 0;
// Initate a switch from the DocumentChannel to the protocol-specific
// real channel.

View File

@ -353,31 +353,56 @@ DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode) {
return NS_OK;
}
IPCResult DocumentChannelChild::RecvConfirmRedirect(
LoadInfoArgs&& aLoadInfo, nsIURI* aNewUri,
ConfirmRedirectResolver&& aResolve) {
// This is effectively the same as AsyncOnChannelRedirect, except since we're
// not propagating the redirect into this process, we don't have an nsIChannel
// for the redirection and we have to do the checks manually.
// This just checks CSP thus far, hopefully there's not much else needed.
RefPtr<dom::Document> cspToInheritLoadingDocument;
nsCOMPtr<nsIContentSecurityPolicy> policy = mLoadState->Csp();
if (policy) {
mozilla::ipc::IPCResult DocumentChannelChild::RecvCSPViolation(
const CSPInfo& aCSP, bool aIsCspToInherit, nsIURI* aBlockedURI,
uint32_t aBlockedContentSource, nsIURI* aOriginalURI,
const nsAString& aViolatedDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject) {
if (aBlockedContentSource > nsCSPContext::BlockedContentSource::eSelf) {
return IPC_FAIL(this, "Invalid BlockedContentSource value");
}
nsCSPContext::BlockedContentSource blockedContentSource =
static_cast<nsCSPContext::BlockedContentSource>(aBlockedContentSource);
RefPtr<dom::Document> cspLoadingDocument;
if (aIsCspToInherit) {
// If this is the cspToInherit from the loadstate, then it should match
// the csp from the load state. Copy across the loading context from
// that policy.
nsCOMPtr<nsIContentSecurityPolicy> policy = mLoadState->Csp();
MOZ_ASSERT(policy,
"How is this a CSP to inherit violation if we didn't have a "
"policy to inherit!");
nsWeakPtr ctx =
static_cast<nsCSPContext*>(policy.get())->GetLoadingContext();
cspToInheritLoadingDocument = do_QueryReferent(ctx);
cspLoadingDocument = do_QueryReferent(ctx);
} else {
// Otherwise we're the normal csp (preload csp is never used for
// Documents), so the loading context is that of our embedder
// Element. Note that this won't necessarily work with fission
// enabled, since the embedder Element might be OOP, bug 1625366
// is filed to fix this.
nsCOMPtr<nsINode> loadingContext;
RefPtr<BrowsingContext> frameBrowsingContext =
GetDocShell()->GetBrowsingContext();
if (frameBrowsingContext) {
loadingContext = frameBrowsingContext->GetEmbedderElement();
if (loadingContext) {
cspLoadingDocument = loadingContext->OwnerDoc();
}
}
}
nsCOMPtr<nsIContentSecurityPolicy> csp =
CSPInfoToCSP(aCSP, cspLoadingDocument);
if (!csp) {
return IPC_OK();
}
nsCOMPtr<nsILoadInfo> loadInfo;
MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(Some(std::move(aLoadInfo)),
cspToInheritLoadingDocument,
getter_AddRefs(loadInfo)));
nsCOMPtr<nsIURI> originalUri;
GetOriginalURI(getter_AddRefs(originalUri));
Maybe<nsresult> cancelCode;
nsresult rv = CSPService::ConsultCSPForRedirect(originalUri, aNewUri,
loadInfo, cancelCode);
aResolve(Tuple<const nsresult&, const Maybe<nsresult>&>(rv, cancelCode));
nsCSPContext::AsyncReportViolation(
static_cast<nsCSPContext*>(csp.get()), nullptr, nullptr, aBlockedURI,
blockedContentSource, aOriginalURI, aViolatedDirective,
aViolatedPolicyIndex, aObserverSubject, nsString(), nsString(), 0, 0);
return IPC_OK();
}

View File

@ -11,6 +11,7 @@
#include "mozilla/net/PDocumentChannelChild.h"
#include "mozilla/net/DocumentChannel.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "mozilla/dom/nsCSPContext.h"
namespace mozilla {
namespace net {
@ -48,9 +49,11 @@ class DocumentChannelChild final : public DocumentChannel,
mozilla::ipc::IPCResult RecvAttachStreamFilter(
Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
mozilla::ipc::IPCResult RecvConfirmRedirect(
LoadInfoArgs&& aLoadInfo, nsIURI* aNewUri,
ConfirmRedirectResolver&& aResolve);
mozilla::ipc::IPCResult RecvCSPViolation(
const CSPInfo& aCSP, bool aIsCspToInherit, nsIURI* aBlockedURI,
uint32_t aBlockedContentSource, nsIURI* aOriginalURI,
const nsAString& aViolatedDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject);
private:
void ShutdownListeners(nsresult aStatusCode);

View File

@ -73,6 +73,19 @@ DocumentChannelParent::RedirectToRealChannel(uint32_t aRedirectFlags,
return SendRedirectToRealChannel(args);
}
void DocumentChannelParent::CSPViolation(
nsCSPContext* aContext, bool aIsCspToInherit, nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex, const nsAString& aObserverSubject) {
CSPInfo cspInfo;
Unused << NS_WARN_IF(NS_FAILED(CSPToCSPInfo(aContext, &cspInfo)));
Unused << SendCSPViolation(
cspInfo, aIsCspToInherit, aBlockedURI, aBlockedContentSource,
aOriginalURI, PromiseFlatString(aViolatedDirective), aViolatedPolicyIndex,
PromiseFlatString(aObserverSubject));
}
} // namespace net
} // namespace mozilla

View File

@ -61,10 +61,12 @@ class DocumentChannelParent final : public ADocumentChannelBridge,
}
}
RefPtr<PDocumentChannelParent::ConfirmRedirectPromise> ConfirmRedirect(
const LoadInfoArgs& aLoadInfo, nsIURI* aNewURI) override {
return SendConfirmRedirect(aLoadInfo, aNewURI);
}
void CSPViolation(nsCSPContext* aContext, bool aIsCspToInherit,
nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject) override;
virtual ProcessId OtherPid() const override { return IProtocol::OtherPid(); }

View File

@ -1181,41 +1181,65 @@ DocumentLoadListener::AsyncOnChannelRedirect(
return NS_BINDING_ABORTED;
}
// Currently the CSP code expects to run in the content
// process so that it can send events. Send a message to
// our content process to ask CSP if we should allow this
// redirect, and wait for confirmation.
nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->LoadInfo();
Maybe<LoadInfoArgs> loadInfoArgs;
MOZ_ALWAYS_SUCCEEDS(ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
MOZ_ASSERT(loadInfoArgs.isSome());
nsCOMPtr<nsIURI> newUri;
nsresult rv = aNewChannel->GetURI(getter_AddRefs(newUri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback(aCallback);
nsCOMPtr<nsIChannel> oldChannel(aOldChannel);
mDocumentChannelBridge->ConfirmRedirect(*loadInfoArgs, newUri)
->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[callback,
oldChannel](const Tuple<nsresult, Maybe<nsresult>>& aResult) {
if (Get<1>(aResult)) {
oldChannel->Cancel(*Get<1>(aResult));
}
callback->OnRedirectVerifyCallback(Get<0>(aResult));
},
[callback, oldChannel](const mozilla::ipc::ResponseRejectReason) {
oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
callback->OnRedirectVerifyCallback(NS_BINDING_ABORTED);
});
// Clear out our nsIParentChannel functions, since a normal parent
// channel would actually redirect and not have those values on the new one.
// We expect the URI classifier to run on the redirected channel with
// the new URI and set these again.
mIParentChannelFunctions.Clear();
nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->LoadInfo();
nsCOMPtr<nsIURI> originalUri;
nsresult rv = aOldChannel->GetOriginalURI(getter_AddRefs(originalUri));
if (NS_FAILED(rv)) {
aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return rv;
}
nsCOMPtr<nsIURI> newUri;
rv = aNewChannel->GetURI(getter_AddRefs(newUri));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<ADocumentChannelBridge> bridge = mDocumentChannelBridge;
auto callback =
[bridge, loadInfo](
nsCSPContext* aContext, mozilla::dom::Element* aTriggeringElement,
nsICSPEventListener* aCSPEventListener, nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex, const nsAString& aObserverSubject,
const nsAString& aSourceFile, const nsAString& aScriptSample,
uint32_t aLineNum, uint32_t aColumnNum) -> nsresult {
MOZ_ASSERT(!aTriggeringElement);
MOZ_ASSERT(!aCSPEventListener);
MOZ_ASSERT(aSourceFile.IsVoid() || aSourceFile.IsEmpty());
MOZ_ASSERT(aScriptSample.IsVoid() || aScriptSample.IsEmpty());
nsCOMPtr<nsIContentSecurityPolicy> cspToInherit =
loadInfo->GetCspToInherit();
// The CSPContext normally contains the loading Document (used
// for targeting events), but this gets lost when serializing across
// IPDL. We need to know which CSPContext we're serializing, so that
// we can find the right loading Document on the content process
// side.
bool isCspToInherit = (aContext == cspToInherit);
bridge->CSPViolation(aContext, isCspToInherit, aBlockedURI,
aBlockedContentSource, aOriginalURI,
aViolatedDirective, aViolatedPolicyIndex,
aObserverSubject);
return NS_OK;
};
Maybe<nsresult> cancelCode;
rv = CSPService::ConsultCSPForRedirect(callback, originalUri, newUri,
loadInfo, cancelCode);
if (cancelCode) {
aOldChannel->Cancel(*cancelCode);
}
NS_ENSURE_SUCCESS(rv, rv);
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}

View File

@ -55,7 +55,10 @@ child:
async RedirectToRealChannel(RedirectToRealChannelArgs args)
returns (nsresult rv);
async ConfirmRedirect(LoadInfoArgs aLoadInfo, nsIURI aNewURI) returns(nsresult rv, nsresult? cancelCode);
async CSPViolation(CSPInfo aCSP, bool aIsCspToInherit,
nsIURI aBlockedURI, uint32_t aBlockedContentSource,
nsIURI aOriginalURI, nsString aViolatedDirective, uint32_t aViolatedPolicyIndex,
nsString aObserverSubject);
// Tell child to delete channel (all IPDL deletes must be done from child to
// avoid races: see bug 591708).