Bug 1521808 - Implement Cross-Origin-Opener-Policy header r=nika,mayhemer

Differential Revision: https://phabricator.services.mozilla.com/D18119
This commit is contained in:
Valentin Gosu
2019-02-12 12:15:54 +00:00
parent 9d12a11e7f
commit 8b553dfcb8
15 changed files with 239 additions and 8 deletions

View File

@@ -9831,6 +9831,8 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
securityFlags |= nsILoadInfo::SEC_SANDBOXED;
}
// TODO: pass openerPolicy through loadInfo?
RefPtr<LoadInfo> loadInfo =
(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT)
? new LoadInfo(loadingWindow, aLoadState->TriggeringPrincipal(),

View File

@@ -485,7 +485,8 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo,
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetDocumentHasLoaded(),
aLoadInfo->GetIsFromProcessingFrameAttributes());
aLoadInfo->GetIsFromProcessingFrameAttributes(),
aLoadInfo->GetOpenerPolicy());
return NS_OK;
}
@@ -647,6 +648,8 @@ nsresult LoadInfoArgsToLoadInfo(
loadInfo->SetIsFromProcessingFrameAttributes();
}
loadInfo->SetOpenerPolicy(loadInfoArgs.openerPolicy());
loadInfo.forget(outLoadInfo);
return NS_OK;
}
@@ -658,8 +661,8 @@ void LoadInfoToParentLoadInfoForwarder(
false, void_t(), nsILoadInfo::TAINTING_BASIC,
false, // serviceWorkerTaintingSynthesized
false, // documentHasUserInteracted
false // documentHasLoaded
);
false, // documentHasLoaded
nsILoadInfo::OPENER_POLICY_NULL);
return;
}
@@ -672,11 +675,14 @@ void LoadInfoToParentLoadInfoForwarder(
uint32_t tainting = nsILoadInfo::TAINTING_BASIC;
Unused << aLoadInfo->GetTainting(&tainting);
nsILoadInfo::CrossOriginOpenerPolicy openerPolicy =
aLoadInfo->GetOpenerPolicy();
*aForwarderArgsOut = ParentLoadInfoForwarderArgs(
aLoadInfo->GetAllowInsecureRedirectToDataURI(), ipcController, tainting,
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetDocumentHasLoaded());
aLoadInfo->GetDocumentHasLoaded(), openerPolicy);
}
nsresult MergeParentLoadInfoForwarder(
@@ -705,6 +711,8 @@ nsresult MergeParentLoadInfoForwarder(
aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting());
}
// TODO: merge openerPolicy
MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
aForwarderArgs.documentHasUserInteracted()));
MOZ_ALWAYS_SUCCEEDS(

View File

@@ -91,7 +91,8 @@ LoadInfo::LoadInfo(
mServiceWorkerTaintingSynthesized(false),
mDocumentHasUserInteracted(false),
mDocumentHasLoaded(false),
mIsFromProcessingFrameAttributes(false) {
mIsFromProcessingFrameAttributes(false),
mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
MOZ_ASSERT(mLoadingPrincipal);
MOZ_ASSERT(mTriggeringPrincipal);
@@ -374,7 +375,8 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
mServiceWorkerTaintingSynthesized(false),
mDocumentHasUserInteracted(false),
mDocumentHasLoaded(false),
mIsFromProcessingFrameAttributes(false) {
mIsFromProcessingFrameAttributes(false),
mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
// Top-level loads are never third-party
// Grab the information we can out of the window.
MOZ_ASSERT(aOuterWindow);
@@ -481,7 +483,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
mServiceWorkerTaintingSynthesized(false),
mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
mDocumentHasLoaded(rhs.mDocumentHasLoaded),
mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes) {}
mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes),
mOpenerPolicy(rhs.mOpenerPolicy) {}
LoadInfo::LoadInfo(
nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
@@ -560,7 +563,8 @@ LoadInfo::LoadInfo(
mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized),
mDocumentHasUserInteracted(aDocumentHasUserInteracted),
mDocumentHasLoaded(aDocumentHasLoaded),
mIsFromProcessingFrameAttributes(false) {
mIsFromProcessingFrameAttributes(false),
mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
// Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
MOZ_ASSERT(mLoadingPrincipal ||
aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
@@ -1399,5 +1403,17 @@ LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetOpenerPolicy(nsILoadInfo::CrossOriginOpenerPolicy* aOpenerPolicy) {
*aOpenerPolicy = mOpenerPolicy;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::SetOpenerPolicy(nsILoadInfo::CrossOriginOpenerPolicy aOpenerPolicy) {
mOpenerPolicy = aOpenerPolicy;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@@ -204,6 +204,8 @@ class LoadInfo final : public nsILoadInfo {
// browsing context container.
// See nsILoadInfo.isFromProcessingFrameAttributes
bool mIsFromProcessingFrameAttributes;
nsILoadInfo::CrossOriginOpenerPolicy mOpenerPolicy;
};
} // namespace net

View File

@@ -1072,4 +1072,15 @@ interface nsILoadInfo : nsISupports
* or https://html.spec.whatwg.org/multipage/obsolete.html#process-the-frame-attributes
*/
[infallible] readonly attribute boolean isFromProcessingFrameAttributes;
cenum CrossOriginOpenerPolicy : 8 {
OPENER_POLICY_NULL = 0,
OPENER_POLICY_SAME_ORIGIN = 1,
OPENER_POLICY_SAME_SITE = 2,
OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG = 0x80,
OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING = OPENER_POLICY_SAME_ORIGIN | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG,
OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING = OPENER_POLICY_SAME_SITE | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG
};
[infallible] attribute nsILoadInfo_CrossOriginOpenerPolicy openerPolicy;
};

View File

@@ -22,6 +22,7 @@ using ArrayOfStringPairs from "mozilla/net/PHttpChannelParams.h";
using struct nsHttpAtom from "nsHttp.h";
using class mozilla::net::nsHttpResponseHead from "nsHttpResponseHead.h";
using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
using nsILoadInfo::CrossOriginOpenerPolicy from "mozilla/net/NeckoMessageUtils.h";
namespace mozilla {
namespace net {
@@ -111,6 +112,7 @@ struct LoadInfoArgs
bool documentHasUserInteracted;
bool documentHasLoaded;
bool isFromProcessingFrameAttributes;
CrossOriginOpenerPolicy openerPolicy;
};
/**
@@ -151,6 +153,8 @@ struct ParentLoadInfoForwarderArgs
bool documentHasUserInteracted;
bool documentHasLoaded;
CrossOriginOpenerPolicy openerPolicy;
// IMPORTANT: when you add new properites here you must also update
// LoadInfoToParentLoadInfoForwarder and MergeParentLoadInfoForwarder
// in BackgroundUtils.cpp/.h!

View File

@@ -16,6 +16,7 @@
#include "prio.h"
#include "mozilla/net/DNS.h"
#include "TimingStruct.h"
#include "nsILoadInfo.h"
namespace IPC {
@@ -186,6 +187,21 @@ struct ParamTraits<nsIHttpChannel::FlashPluginState>
nsIHttpChannel::FlashPluginState, nsIHttpChannel::FlashPluginUnknown,
nsIHttpChannel::FlashPluginLastValue> {};
struct CrossOriginOpenerPolicyValidator {
static bool IsLegalValue(nsILoadInfo::CrossOriginOpenerPolicy e) {
return e == nsILoadInfo::OPENER_POLICY_NULL ||
e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN ||
e == nsILoadInfo::OPENER_POLICY_SAME_SITE ||
e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING ||
e == nsILoadInfo::OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING;
}
};
template <>
struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy>
: EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy,
CrossOriginOpenerPolicyValidator> {};
} // namespace IPC
#endif // mozilla_net_NeckoMessageUtils_h

View File

@@ -2232,6 +2232,11 @@ HttpBaseChannel::SwitchProcessTo(mozilla::dom::Promise* aTabParent,
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch(bool* aMismatch) {
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HttpBaseChannel::UpgradeToSecure() {
// Upgrades are handled internally between http-on-modify-request and

View File

@@ -221,6 +221,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
NS_IMETHOD RedirectTo(nsIURI *newURI) override;
NS_IMETHOD SwitchProcessTo(mozilla::dom::Promise *aTabParent,
uint64_t aIdentifier) override;
NS_IMETHOD HasCrossOriginOpenerPolicyMismatch(bool *aMismatch) override;
NS_IMETHOD UpgradeToSecure() override;
NS_IMETHOD GetRequestContextID(uint64_t *aRCID) override;
NS_IMETHOD GetTransferSize(uint64_t *aTransferSize) override;

View File

@@ -274,6 +274,11 @@ NullHttpChannel::SwitchProcessTo(mozilla::dom::Promise *aTabParent,
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
NullHttpChannel::HasCrossOriginOpenerPolicyMismatch(bool *aMismatch) {
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
NullHttpChannel::UpgradeToSecure() { return NS_ERROR_NOT_IMPLEMENTED; }

View File

@@ -40,6 +40,7 @@ HTTP_ATOM(Content_MD5, "Content-MD5")
HTTP_ATOM(Content_Range, "Content-Range")
HTTP_ATOM(Content_Type, "Content-Type")
HTTP_ATOM(Cookie, "Cookie")
HTTP_ATOM(Cross_Origin_Opener_Policy, "Cross-Origin-Opener-Policy")
HTTP_ATOM(Date, "Date")
HTTP_ATOM(DAV, "DAV")
HTTP_ATOM(Depth, "Depth")

View File

@@ -123,6 +123,7 @@
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/net/AsyncUrlChannelClassifier.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsIWebNavigation.h"
#ifdef MOZ_TASK_TRACER
# include "GeckoTaskTracer.h"
@@ -7152,6 +7153,147 @@ nsresult nsHttpChannel::StartCrossProcessRedirect() {
return rv;
}
static nsILoadInfo::CrossOriginOpenerPolicy GetCrossOriginOpenerPolicy(
nsHttpResponseHead *responseHead) {
MOZ_ASSERT(responseHead);
nsAutoCString openerPolicy;
Unused << responseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
openerPolicy);
// Cross-Origin-Opener-Policy = sameness [ RWS outgoing ]
// sameness = %s"same-origin" / %s"same-site" ; case-sensitive
// outgoing = %s"unsafe-allow-outgoing" ; case-sensitive
Tokenizer t(openerPolicy);
nsAutoCString sameness;
nsAutoCString outgoing;
// The return value will be true if we find any whitespace. If there is
// whitespace, then it must be followed by "unsafe-allow-outgoing" otherwise
// this is a malformed header value.
bool allowOutgoing = t.ReadUntil(Tokenizer::Token::Whitespace(), sameness);
if (allowOutgoing) {
t.SkipWhites();
bool foundEOF = t.ReadUntil(Tokenizer::Token::EndOfFile(), outgoing);
if (!foundEOF) {
// Malformed response. There should be no text after the second token.
return nsILoadInfo::OPENER_POLICY_NULL;
}
if (!outgoing.EqualsLiteral("unsafe-allow-outgoing")) {
// Malformed response. Only one allowed value for the second token.
return nsILoadInfo::OPENER_POLICY_NULL;
}
}
nsILoadInfo::CrossOriginOpenerPolicy policy = nsILoadInfo::OPENER_POLICY_NULL;
if (sameness.EqualsLiteral("same-origin")) {
policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
if (allowOutgoing) {
policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING;
}
} else if (sameness.EqualsLiteral("same-site")) {
policy = nsILoadInfo::OPENER_POLICY_SAME_SITE;
if (allowOutgoing) {
policy = nsILoadInfo::OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING;
}
}
return policy;
}
static bool CompareCrossOriginOpenerPolicies(
nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
nsIPrincipal *documentOrigin,
nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
nsIPrincipal *resultOrigin) {
if (documentPolicy == nsILoadInfo::OPENER_POLICY_NULL &&
resultPolicy == nsILoadInfo::OPENER_POLICY_NULL) {
return true;
}
if (documentPolicy != resultPolicy) {
return false;
}
// For the next checks the document and result will have matching policies.
// We either check if they are same origin or same site.
if ((documentPolicy & nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) &&
documentOrigin->Equals(resultOrigin)) {
return true;
}
if (documentPolicy & nsILoadInfo::OPENER_POLICY_SAME_SITE) {
nsAutoCString siteOriginA;
nsAutoCString siteOriginB;
documentOrigin->GetSiteOrigin(siteOriginA);
resultOrigin->GetSiteOrigin(siteOriginB);
if (siteOriginA == siteOriginB) {
return true;
}
}
return false;
}
NS_IMETHODIMP
nsHttpChannel::HasCrossOriginOpenerPolicyMismatch(bool *aMismatch) {
MOZ_ASSERT(aMismatch);
if (!aMismatch) {
return NS_ERROR_INVALID_ARG;
}
*aMismatch = false;
// Only consider Cross-Origin-Opener-Policy for toplevel document loads.
if (mLoadInfo->GetExternalContentPolicyType() !=
nsIContentPolicy::TYPE_DOCUMENT) {
return NS_OK;
}
// Maybe the channel failed and we have no response head?
nsHttpResponseHead *head =
mResponseHead ? mResponseHead : mCachedResponseHead;
if (!head) {
return NS_ERROR_NOT_AVAILABLE;
}
// Get the policy of the active document, and the policy for the result.
nsILoadInfo::CrossOriginOpenerPolicy documentPolicy =
mLoadInfo->GetOpenerPolicy();
nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
GetCrossOriginOpenerPolicy(head);
// We use the top window principal as the documentOrigin
if (!mTopWindowPrincipal) {
GetTopWindowPrincipal(getter_AddRefs(mTopWindowPrincipal));
}
nsCOMPtr<nsIPrincipal> documentOrigin = mTopWindowPrincipal;
nsCOMPtr<nsIPrincipal> resultOrigin;
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
this, getter_AddRefs(resultOrigin));
if (!CompareCrossOriginOpenerPolicies(documentPolicy, documentOrigin,
resultPolicy, resultOrigin)) {
// If one of the following is false:
// - doc is the initial about:blank document
// - document's unsafe-allow-outgoing is true
// - resultPolicy is null
// then we have a mismatch.
if (!documentOrigin->GetIsNullPrincipal() ||
!(documentPolicy &
nsILoadInfo::OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG) ||
!(resultPolicy == nsILoadInfo::OPENER_POLICY_NULL)) {
*aMismatch = true;
return NS_OK;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
nsresult rv;

View File

@@ -164,6 +164,7 @@ class nsHttpChannel final : public HttpBaseChannel,
NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
NS_IMETHOD SwitchProcessTo(mozilla::dom::Promise *aTabParent,
uint64_t aIdentifier) override;
NS_IMETHOD HasCrossOriginOpenerPolicyMismatch(bool *aMismatch) override;
// nsIHttpChannelInternal
NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;

View File

@@ -457,6 +457,13 @@ interface nsIHttpChannel : nsIChannel
[must_use] void switchProcessTo(in Promise aTabPromise,
in unsigned long long aIdentifier);
/**
* Used to determine if there is a Cross-Origin-Opener-Policy mismatch
* that would require switching the channel to another process.
* @throws NS_ERROR_NOT_AVAILABLE if we don't have a responseHead
*/
[must_use] boolean hasCrossOriginOpenerPolicyMismatch();
/**
* Flags a channel to be upgraded to HTTPS.
*

View File

@@ -1008,6 +1008,16 @@ nsViewSourceChannel::SwitchProcessTo(mozilla::dom::Promise *aTabParent,
: mHttpChannel->SwitchProcessTo(aTabParent, aIdentifier);
}
NS_IMETHODIMP
nsViewSourceChannel::HasCrossOriginOpenerPolicyMismatch(bool *aMismatch) {
MOZ_ASSERT(aMismatch);
if (!aMismatch) {
return NS_ERROR_INVALID_ARG;
}
*aMismatch = false;
return NS_OK;
}
NS_IMETHODIMP
nsViewSourceChannel::UpgradeToSecure() {
return !mHttpChannel ? NS_ERROR_NULL_POINTER