Bug 1579992 - Load pages into new webCOOP+COEP process type r=nika

* This patch makes pages with the `OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP` policy load into a special `webCOOP+COEP={pageOrigin}` remote type.
* Adds `E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX="webCOOP+COEP="`
* When a COOP process switch occurs and the target page doesn't have this policy, we pass a `preferredRemoteType="web"` into `E10SUtils.getRemoteTypeForPrincipal` ensuring that we correctly get a different `remoteType`
* E10SUtils.getRemoteTypeForPrincipal is changed such that `if preferredRemoteType.startsWith(WEB_REMOTE_COOP_COEP_TYPE_PREFIX)` we don't override it with `webIsolated={pageOrigin}`.
* `coop_header.sjs` is changed to also allow setting `Cross-Origin-Embedder-Policy` headers
* `browser_httpCrossOriginOpenerPolicy.js` is changed to test that pages are correctly opened in the correct remoteType process.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Valentin Gosu 2019-10-21 16:56:00 +00:00
Родитель 962dd7ee27
Коммит 74ec329cc9
13 изменённых файлов: 239 добавлений и 59 удалений

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

@ -2699,18 +2699,43 @@ var SessionStoreInternal = {
let resultPrincipal = Services.scriptSecurityManager.getChannelResultPrincipal(
channel
);
const isCOOPSwitch =
E10SUtils.useCrossOriginOpenerPolicy() &&
switchRequestor.hasCrossOriginOpenerPolicyMismatch();
let preferredRemoteType = currentRemoteType;
if (
switchRequestor.crossOriginOpenerPolicy ==
Ci.nsILoadInfo.OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP
) {
// We want documents with a SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP
// COOP policy to be loaded in a separate process for which we can enable
// high resolution timers.
preferredRemoteType =
E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX + resultPrincipal.siteOrigin;
} else if (isCOOPSwitch) {
// If it is a coop switch, but doesn't have this flag, we want to switch
// to a default remoteType
preferredRemoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
}
debug(
`[process-switch]: currentRemoteType (${currentRemoteType}) preferredRemoteType: ${preferredRemoteType}`
);
let remoteType = E10SUtils.getRemoteTypeForPrincipal(
resultPrincipal,
true,
useRemoteSubframes,
currentRemoteType,
preferredRemoteType,
currentPrincipal
);
if (
currentRemoteType == remoteType &&
(!E10SUtils.useCrossOriginOpenerPolicy() ||
!switchRequestor.hasCrossOriginOpenerPolicyMismatch())
) {
debug(
`[process-switch]: ${currentRemoteType}, ${remoteType}, ${isCOOPSwitch}`
);
if (currentRemoteType == remoteType && !isCOOPSwitch) {
debug(`[process-switch]: type (${remoteType}) is compatible - ignoring`);
return;
}
@ -2723,10 +2748,6 @@ var SessionStoreInternal = {
return;
}
const isCOOPSwitch =
E10SUtils.useCrossOriginOpenerPolicy() &&
switchRequestor.hasCrossOriginOpenerPolicyMismatch();
// ------------------------------------------------------------------------
// DANGER ZONE: Perform a process switch into the new process. This is
// destructive.

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

@ -80,7 +80,7 @@ already_AddRefed<WindowGlobalChild> WindowGlobalChild::Create(
if (httpChan &&
loadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_DOCUMENT &&
NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(
NS_SUCCEEDED(httpChan->ComputeCrossOriginOpenerPolicy(
nsILoadInfo::OPENER_POLICY_NULL, &policy))) {
bc->SetOpenerPolicy(policy);
}

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

@ -42,4 +42,11 @@ interface nsIProcessSwitchRequestor : nsISupports
* @throws NS_ERROR_NOT_AVAILABLE if we don't have a responseHead
*/
[must_use] boolean hasCrossOriginOpenerPolicyMismatch();
/**
* Returns a cached CrossOriginOpenerPolicy that is computed just before we
* determine if there is a policy mismatch.
* @throws NS_ERROR_NOT_AVAILABLE if it has not been computed yet
*/
[must_use] readonly attribute nsILoadInfo_CrossOriginOpenerPolicy crossOriginOpenerPolicy;
};

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

@ -904,5 +904,22 @@ DocumentChannelParent::HasCrossOriginOpenerPolicyMismatch(bool* aMismatch) {
return channel->HasCrossOriginOpenerPolicyMismatch(aMismatch);
}
NS_IMETHODIMP
DocumentChannelParent::GetCrossOriginOpenerPolicy(
nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
MOZ_ASSERT(aPolicy);
if (!aPolicy) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsHttpChannel> channel = do_QueryInterface(mChannel);
if (!channel) {
*aPolicy = nsILoadInfo::OPENER_POLICY_NULL;
return NS_OK;
}
return channel->GetCrossOriginOpenerPolicy(aPolicy);
}
} // namespace net
} // namespace mozilla

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

@ -645,7 +645,7 @@ bool ClassifierDummyChannel::GetHasNonEmptySandboxingFlag() { return false; }
void ClassifierDummyChannel::SetHasNonEmptySandboxingFlag(
bool aHasNonEmptySandboxingFlag) {}
NS_IMETHODIMP ClassifierDummyChannel::GetCrossOriginOpenerPolicy(
NS_IMETHODIMP ClassifierDummyChannel::ComputeCrossOriginOpenerPolicy(
nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
return NS_ERROR_NOT_IMPLEMENTED;

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

@ -4463,7 +4463,7 @@ nsILoadInfo::CrossOriginOpenerPolicy CreateCrossOriginOpenerPolicy(
// Obtain a cross-origin opener-policy from a response response and a
// cross-origin opener policy initiator.
// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
NS_IMETHODIMP HttpBaseChannel::GetCrossOriginOpenerPolicy(
NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy(
nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
MOZ_ASSERT(aOutPolicy);

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

@ -313,7 +313,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
NS_IMETHOD CancelByURLClassifier(nsresult aErrorCode) override;
virtual void SetIPv4Disabled(void) override;
virtual void SetIPv6Disabled(void) override;
NS_IMETHOD GetCrossOriginOpenerPolicy(
NS_IMETHOD ComputeCrossOriginOpenerPolicy(
nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) override;
virtual bool GetHasNonEmptySandboxingFlag() override {
@ -590,10 +590,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
nsresult GetResponseEmbedderPolicy(
nsILoadInfo::CrossOriginEmbedderPolicy* aResponseEmbedderPolicy);
nsresult GetCrossOriginOpenerPolicyWithInitiator(
nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy);
friend class PrivateBrowsingChannel<HttpBaseChannel>;
friend class InterceptFailedOnStop;

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

@ -7289,6 +7289,23 @@ nsHttpChannel::HasCrossOriginOpenerPolicyMismatch(bool* aMismatch) {
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetCrossOriginOpenerPolicy(
nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
MOZ_ASSERT(aPolicy);
if (!aPolicy) {
return NS_ERROR_INVALID_ARG;
}
// If this method is called before OnStartRequest (ie. before we call
// ComputeCrossOriginOpenerPolicy) or if we were unable to compute the
// policy we'll throw an error.
if (!mComputedCrossOriginOpenerPolicy.isSome()) {
return NS_ERROR_NOT_AVAILABLE;
}
*aPolicy = mComputedCrossOriginOpenerPolicy.value();
return NS_OK;
}
nsresult nsHttpChannel::StartCrossProcessRedirect() {
nsresult rv;
@ -7379,7 +7396,8 @@ nsresult nsHttpChannel::ComputeCrossOriginOpenerPolicyMismatch() {
nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
nsILoadInfo::OPENER_POLICY_NULL;
Unused << GetCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
Unused << ComputeCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
mComputedCrossOriginOpenerPolicy.emplace(resultPolicy);
// If bc's popup sandboxing flag set is not empty and potentialCOOP is
// non-null, then navigate bc to a network error and abort these steps.

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

@ -648,6 +648,10 @@ class nsHttpChannel final : public HttpBaseChannel,
static const uint32_t WAIT_FOR_CACHE_ENTRY = 1;
static const uint32_t WAIT_FOR_OFFLINE_CACHE_ENTRY = 2;
// Gets computed during ComputeCrossOriginOpenerPolicyMismatch so we have
// the channel's policy even if we don't know policy initiator.
Maybe<nsILoadInfo::CrossOriginOpenerPolicy> mComputedCrossOriginOpenerPolicy;
bool mCacheOpenWithPriority;
uint32_t mCacheQueueSizeWhenOpen;

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

@ -394,7 +394,7 @@ interface nsIHttpChannelInternal : nsISupports
* Get the Cross-Origin-Opener-Policy of the top-level document channel.
*/
[noscript]
nsILoadInfo_CrossOriginOpenerPolicy getCrossOriginOpenerPolicy(
nsILoadInfo_CrossOriginOpenerPolicy computeCrossOriginOpenerPolicy(
in nsILoadInfo_CrossOriginOpenerPolicy aInitiatorPolicy);
[notxpcom, nostdcall] attribute boolean hasNonEmptySandboxingFlag;

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

@ -25,7 +25,13 @@ async function performLoad(browser, opts, action) {
await loadedPromise;
}
async function test_coop(start, target, expectedProcessSwitch) {
async function test_coop(
start,
target,
expectedProcessSwitch,
startRemoteTypeCheck,
targetRemoteTypeCheck
) {
return BrowserTestUtils.withNewTab(
{
gBrowser,
@ -37,11 +43,16 @@ async function test_coop(start, target, expectedProcessSwitch) {
await new Promise(resolve => setTimeout(resolve, 20));
let browser = gBrowser.selectedBrowser;
let firstProcessID = await ContentTask.spawn(browser, null, () => {
return Services.appinfo.processID;
});
let firstRemoteType = browser.remoteType;
let firstProcessID = browser.frameLoader.remoteTab.osPid;
info(`firstProcessID: ${firstProcessID}`);
info(
`firstProcessID: ${firstProcessID} firstRemoteType: ${firstRemoteType}`
);
if (startRemoteTypeCheck) {
startRemoteTypeCheck(firstRemoteType);
}
await performLoad(
browser,
@ -63,11 +74,15 @@ async function test_coop(start, target, expectedProcessSwitch) {
info(`Navigated to: ${target}`);
await new Promise(resolve => setTimeout(resolve, 20));
browser = gBrowser.selectedBrowser;
let secondProcessID = await ContentTask.spawn(browser, null, () => {
return Services.appinfo.processID;
});
let secondRemoteType = browser.remoteType;
let secondProcessID = browser.frameLoader.remoteTab.osPid;
info(`secondProcessID: ${secondProcessID}`);
info(
`secondProcessID: ${secondProcessID} secondRemoteType: ${secondRemoteType}`
);
if (targetRemoteTypeCheck) {
targetRemoteTypeCheck(secondRemoteType);
}
if (expectedProcessSwitch) {
Assert.notEqual(
firstProcessID,
@ -118,7 +133,7 @@ async function test_download_from(initCoop, downloadCoop) {
info(`test_download: Test tab ready`);
let start = httpURL(
"coop_header.sjs?downloadPage&" + initCoop,
"coop_header.sjs?downloadPage&coop=" + initCoop,
"https://example.com"
);
await performLoad(
@ -195,7 +210,10 @@ add_task(async function test_multiple_nav_process_switches() {
Assert.equal(prevPID, currentPID);
prevPID = currentPID;
target = httpURL("coop_header.sjs?same-origin", "https://example.org");
target = httpURL(
"coop_header.sjs?coop=same-origin",
"https://example.org"
);
await performLoad(
browser,
{
@ -219,7 +237,10 @@ add_task(async function test_multiple_nav_process_switches() {
Assert.notEqual(prevPID, currentPID);
prevPID = currentPID;
target = httpURL("coop_header.sjs?same-origin", "https://example.com");
target = httpURL(
"coop_header.sjs?coop=same-origin",
"https://example.com"
);
await performLoad(
browser,
{
@ -243,7 +264,10 @@ add_task(async function test_multiple_nav_process_switches() {
Assert.notEqual(prevPID, currentPID);
prevPID = currentPID;
target = httpURL("coop_header.sjs?same-origin.#4", "https://example.com");
target = httpURL(
"coop_header.sjs?coop=same-origin&index=4",
"https://example.com"
);
await performLoad(
browser,
{
@ -274,43 +298,120 @@ add_task(async function test_disabled() {
false
);
await test_coop(
httpURL("coop_header.sjs?same-origin", "http://example.com"),
httpURL("coop_header.sjs?coop=same-origin", "http://example.com"),
httpURL("coop_header.sjs", "http://example.com"),
false
);
await test_coop(
httpURL("coop_header.sjs", "http://example.com"),
httpURL("coop_header.sjs?same-origin", "http://example.com"),
httpURL("coop_header.sjs?coop=same-origin", "http://example.com"),
false
);
await test_coop(
httpURL("coop_header.sjs?same-origin", "http://example.com"),
httpURL("coop_header.sjs?same-site", "http://example.com"),
httpURL("coop_header.sjs?coop=same-origin", "http://example.com"),
httpURL("coop_header.sjs?coop=same-site", "http://example.com"),
false
); // assuming we don't have fission yet :)
});
add_task(async function test_enabled() {
await SpecialPowers.pushPrefEnv({ set: [[PREF_NAME, true]] });
function checkIsCoopRemoteType(remoteType) {
Assert.ok(
remoteType.startsWith(E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX),
`${remoteType} expected to be coop`
);
}
function checkIsNotCoopRemoteType(remoteType) {
if (gFissionBrowser) {
Assert.ok(
remoteType.startsWith("webIsolated="),
`${remoteType} expected to start with webIsolated=`
);
} else {
Assert.equal(
remoteType,
E10SUtils.WEB_REMOTE_TYPE,
`${remoteType} expected to be web`
);
}
}
await test_coop(
httpURL("coop_header.sjs", "https://example.com"),
httpURL("coop_header.sjs", "https://example.com"),
false
false,
checkIsNotCoopRemoteType,
checkIsNotCoopRemoteType
);
await test_coop(
httpURL("coop_header.sjs", "https://example.com"),
httpURL("coop_header.sjs?same-origin", "https://example.org"),
true
httpURL("coop_header.sjs?coop=same-origin", "https://example.org"),
true,
checkIsNotCoopRemoteType,
checkIsNotCoopRemoteType
);
await test_coop(
httpURL("coop_header.sjs?same-origin#1", "https://example.com"),
httpURL("coop_header.sjs?same-origin#1", "https://example.org"),
true
httpURL("coop_header.sjs?coop=same-origin&index=1", "https://example.com"),
httpURL("coop_header.sjs?coop=same-origin&index=1", "https://example.org"),
true,
checkIsNotCoopRemoteType,
checkIsNotCoopRemoteType
);
await test_coop(
httpURL("coop_header.sjs?same-origin#2", "https://example.com"),
httpURL("coop_header.sjs?same-site#2", "https://example.org"),
true
httpURL("coop_header.sjs?coop=same-origin&index=2", "https://example.com"),
httpURL("coop_header.sjs?coop=same-site&index=2", "https://example.org"),
true,
checkIsNotCoopRemoteType,
checkIsNotCoopRemoteType
);
await test_coop(
httpURL("coop_header.sjs", "https://example.com"),
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp",
"https://example.com"
),
true,
checkIsNotCoopRemoteType,
checkIsCoopRemoteType
);
await test_coop(
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp&index=2",
"https://example.com"
),
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp&index=3",
"https://example.com"
),
false,
checkIsCoopRemoteType,
checkIsCoopRemoteType
);
await test_coop(
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp&index=4",
"https://example.com"
),
httpURL("coop_header.sjs", "https://example.com"),
true,
checkIsCoopRemoteType,
checkIsNotCoopRemoteType
);
await test_coop(
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp&index=5",
"https://example.com"
),
httpURL(
"coop_header.sjs?coop=same-origin&coep=require-corp&index=6",
"https://example.org"
),
true,
checkIsCoopRemoteType,
checkIsCoopRemoteType
);
});

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

@ -1,22 +1,24 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 200, "OK");
Components.utils.importGlobalProperties(["URLSearchParams"]);
let query = new URLSearchParams(request.queryString);
let qs = request.queryString.replace(/\./g, '');
response.setStatusLine(request.httpVersion, 200, "OK");
let isDownloadPage = false;
let isDownloadFile = false;
if (qs.length > 0) {
qs.split("&").forEach(param => {
if (param === "downloadPage") {
isDownloadPage = true;
} else if (param === "downloadFile") {
isDownloadFile = true;
} else if (param.length > 0) {
response.setHeader("Cross-Origin-Opener-Policy", unescape(param), false);
}
});
}
query.forEach((value, name) => {
if (name === "downloadPage") {
isDownloadPage = true;
} else if (name === "downloadFile") {
isDownloadFile = true;
} else if (name == "coop") {
response.setHeader("Cross-Origin-Opener-Policy", unescape(value), false);
} else if (name == "coep") {
response.setHeader("Cross-Origin-Embedder-Policy", unescape(value), false);
}
});
let downloadHTML = "";
if (isDownloadPage) {

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

@ -96,6 +96,7 @@ const NOT_REMOTE = null;
// These must match any similar ones in ContentParent.h and ProcInfo.h
const WEB_REMOTE_TYPE = "web";
const FISSION_WEB_REMOTE_TYPE_PREFIX = "webIsolated=";
const WEB_REMOTE_COOP_COEP_TYPE_PREFIX = "webCOOP+COEP=";
const FILE_REMOTE_TYPE = "file";
const EXTENSION_REMOTE_TYPE = "extension";
const PRIVILEGEDABOUT_REMOTE_TYPE = "privilegedabout";
@ -210,6 +211,18 @@ function validatedWebRemoteType(
// question, and use it to generate an isolated origin.
if (aRemoteSubframes) {
let targetPrincipal = sm.createContentPrincipal(aTargetUri, {});
// If this is a special webCOOP+COEP= remote type that matches the
// principal's siteOrigin, we don't want to override it with webIsolated=
// as it's already isolated.
if (
aPreferredRemoteType &&
aPreferredRemoteType ==
`${WEB_REMOTE_COOP_COEP_TYPE_PREFIX}${targetPrincipal.siteOrigin}`
) {
return aPreferredRemoteType;
}
return FISSION_WEB_REMOTE_TYPE_PREFIX + targetPrincipal.siteOrigin;
}
@ -249,6 +262,7 @@ var E10SUtils = {
DEFAULT_REMOTE_TYPE,
NOT_REMOTE,
WEB_REMOTE_TYPE,
WEB_REMOTE_COOP_COEP_TYPE_PREFIX,
FILE_REMOTE_TYPE,
EXTENSION_REMOTE_TYPE,
PRIVILEGEDABOUT_REMOTE_TYPE,