Bug 1682450 - Parse only the final SameSite attribute when mutliple are present; r=baku

Differential Revision: https://phabricator.services.mozilla.com/D99740
This commit is contained in:
Steven Englehardt 2020-12-21 19:24:30 +00:00
Родитель 0555a907d0
Коммит 0ebdba6f2c
6 изменённых файлов: 237 добавлений и 6 удалений

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

@ -1335,6 +1335,16 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter,
return false;
}
static inline void SetSameSiteDefaultAttribute(CookieStruct& aCookieData,
bool laxByDefault) {
aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
if (laxByDefault) {
aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
} else {
aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
}
}
// Parses attributes from cookie header. expires/max-age attributes aren't
// folded into the cookie struct here, because we don't know which one to use
// until we've parsed the header.
@ -1364,17 +1374,12 @@ bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
aCookieData.isSecure() = false;
aCookieData.isHttpOnly() = false;
aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
bool laxByDefault =
StaticPrefs::network_cookie_sameSite_laxByDefault() &&
!nsContentUtils::IsURIInPrefList(
aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
if (laxByDefault) {
aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
}
SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
nsDependentCSubstring tokenString(cookieStart, cookieStart);
nsDependentCSubstring tokenValue(cookieStart, cookieStart);
@ -1438,6 +1443,9 @@ bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC,
aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
sameSiteSet = true;
} else {
// Reset to defaults if unknown token value (see Bug 1682450)
SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
sameSiteSet = false;
CookieLogging::LogMessageToConsole(
aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
"CookieSameSiteValueInvalid2"_ns,

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

@ -0,0 +1,21 @@
[multiple-samesite-attributes.https.html]
prefs: [network.cookie.sameSite.laxByDefault:true, network.cookie.sameSite.noneRequiresSecure:true]
expected:
if (os == "mac") and not debug: ["OK", "TIMEOUT"]
[Cross-site redirecting to same-host images are strictly same-site]
expected: FAIL
[Cross-site redirecting to subdomain images are strictly same-site]
expected: FAIL
[multiple-samesite-attributes.https.html?legacy-samesite]
prefs: [network.cookie.sameSite.laxByDefault:false, network.cookie.sameSite.noneRequiresSecure:false]
expected:
if (os == "mac") and not debug: ["OK", "TIMEOUT"]
[Cross-site redirecting to same-host images are strictly same-site]
expected: FAIL
[Cross-site redirecting to subdomain images are strictly same-site]
expected: FAIL

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

@ -299,6 +299,28 @@ function resetSameSiteNoneCookies(origin, value) {
})
}
// Reset test cookies with multiple SameSite attributes on |origin|.
// If |origin| matches `self.origin`, assert (via `document.cookie`)
// that they were properly removed.
function resetSameSiteMultiAttributeCookies(origin, value) {
return credFetch(origin + "/cookies/resources/dropSameSiteMultiAttribute.py")
.then(_ => {
if (origin == self.origin) {
assert_dom_cookie("samesite_unsupported", value, false);
assert_dom_cookie("samesite_unsupported_none", value, false);
assert_dom_cookie("samesite_unsupported_lax", value, false);
assert_dom_cookie("samesite_unsupported_strict", value, false);
assert_dom_cookie("samesite_none_unsupported", value, false);
assert_dom_cookie("samesite_lax_unsupported", value, false);
assert_dom_cookie("samesite_strict_unsupported", value, false);
assert_dom_cookie("samesite_lax_none", value, false);
}
})
.then(_ => {
return credFetch(origin + "/cookies/resources/setSameSiteMultiAttribute.py?" + value);
})
}
//
// DOM based cookie manipulation APIs
//

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

@ -0,0 +1,17 @@
from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders
def main(request, response):
"""Respond to `/cookies/resources/dropSameSiteMultiAttribute.py by dropping
the cookies set by setSameSiteMultiAttribute.py"""
headers = setNoCacheAndCORSHeaders(request, response)
# Expire the cookies, and return a JSON-encoded success code.
headers.append(makeDropCookie(b"samesite_unsupported", True))
headers.append(makeDropCookie(b"samesite_unsupported_none", True))
headers.append(makeDropCookie(b"samesite_unsupported_lax", False))
headers.append(makeDropCookie(b"samesite_unsupported_strict", False))
headers.append(makeDropCookie(b"samesite_none_unsupported", True))
headers.append(makeDropCookie(b"samesite_lax_unsupported", True))
headers.append(makeDropCookie(b"samesite_strict_unsupported", True))
headers.append(makeDropCookie(b"samesite_lax_none", True))
return headers, b'{"success": true}'

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

@ -0,0 +1,60 @@
from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
from wptserve.utils import isomorphic_encode
def main(request, response):
"""Respond to `/cookie/set/samesite?{value}` by setting the following combination of cookies:
1. `samesite_unsupported={value};SameSite=Unsupported;path=/;Secure`
2. `samesite_unsupported_none={value};SameSite=Unsupported;SameSite=None;path=/;Secure`
3. `samesite_unsupported_lax={value};SameSite=Unsupported;SameSite=Lax;path=/`
4. `samesite_unsupported_strict={value};SameSite=Unsupported;SameSite=Strict;path=/`
5. `samesite_none_unsupported={value};SameSite=None;SameSite=Unsupported;path=/;Secure`
6. `samesite_lax_unsupported={value};SameSite=Lax;SameSite=Unsupported;path=/;Secure`
7. `samesite_strict_unsupported={value};SameSite=Strict;SameSite=Unsupported;path=/;Secure`
8. `samesite_lax_none={value};SameSite=Lax;SameSite=None;path=/;Secure`
9. `samesite_lax_strict={value};SameSite=Lax;SameSite=Strict;path=/`
10. `samesite_strict_lax={value};SameSite=Strict;SameSite=Lax;path=/`
Then navigate to a page that will post a message back to the opener with the set cookies"""
headers = setNoCacheAndCORSHeaders(request, response)
value = isomorphic_encode(request.url_parts.query)
headers.append((b"Content-Type", b"text/html; charset=utf-8"))
# Unknown value; single attribute
headers.append(makeCookieHeader(
b"samesite_unsupported", value, {b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
# Multiple attributes; first attribute unknown
headers.append(makeCookieHeader(
b"samesite_unsupported_none", value, {b"SameSite":b"Unsupported", b"SameSite":b"None", b"path":b"/", b"Secure":b""}))
headers.append(makeCookieHeader(
b"samesite_unsupported_lax", value, {b"SameSite":b"Unsupported", b"SameSite":b"Lax", b"path":b"/"}))
headers.append(makeCookieHeader(
b"samesite_unsupported_strict", value, {b"SameSite":b"Unsupported", b"SameSite":b"Strict", b"path":b"/"}))
# Multiple attributes; second attribute unknown
headers.append(makeCookieHeader(
b"samesite_none_unsupported", value, {b"SameSite":b"None", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
headers.append(makeCookieHeader(
b"samesite_lax_unsupported", value, {b"SameSite":b"Lax", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
headers.append(makeCookieHeader(
b"samesite_strict_unsupported", value, {b"SameSite":b"Strict", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
# Multiple attributes; both known
headers.append(makeCookieHeader(
b"samesite_lax_none", value, {b"SameSite":b"Lax", b"SameSite":b"None", b"path":b"/", b"Secure":b""}))
headers.append(makeCookieHeader(
b"samesite_lax_strict", value, {b"SameSite":b"Lax", b"SameSite":b"Strict", b"path":b"/"}))
headers.append(makeCookieHeader(
b"samesite_strict_lax", value, {b"SameSite":b"Strict", b"SameSite":b"Lax", b"path":b"/"}))
document = b"""
<!DOCTYPE html>
<script>
// A same-site navigation, which should attach all cookies including SameSite ones.
// This is necessary because this page may have been reached via a cross-site navigation, so
// we might not have access to some SameSite cookies from here.
window.location = "../samesite/resources/echo-cookies.html";
</script>
"""
return headers, document

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

@ -0,0 +1,103 @@
<!DOCTYPE html>
<meta charset="utf-8"/>
<meta name="timeout" content="long">
<meta name="variant" content="">
<meta name="variant" content="?legacy-samesite">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/cookies/resources/cookie-helper.sub.js"></script>
<script>
function assert_cookie_present(origin, name, value) {
return new Promise((resolve, reject) => {
var img = document.createElement("img");
img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin);
img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin);
// We need to URL encode the destination path/query if we're redirecting:
if (origin.match(/\/redir/))
img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
else
img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
});
}
function assert_cookie_absent(origin, name, value) {
return new Promise((resolve, reject) => {
var img = document.createElement("img");
img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin);
img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin);
// We need to URL encode the destination path/query if we're redirecting:
if (origin.match(/\/redir/))
img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
else
img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
});
}
function create_test(origin, target, expectedStatus, title) {
promise_test(t => {
var value = "" + Math.random();
return resetSameSiteMultiAttributeCookies(origin, value)
.then(_ => {
var asserts = [
assert_cookie_present(target, "samesite_unsupported_none", value),
assert_cookie_present(target, "samesite_lax_none", value),
expectedStatus == SameSiteStatus.STRICT ?
assert_cookie_present(target, "samesite_unsupported_strict", value) :
assert_cookie_absent(target, "samesite_unsupported_strict", value),
expectedStatus == SameSiteStatus.STRICT ?
assert_cookie_present(target, "samesite_lax_strict", value) :
assert_cookie_absent(target, "samesite_lax_strict", value),
expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_unsupported_lax", value) :
assert_cookie_present(target, "samesite_unsupported_lax", value),
expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_strict_lax", value) :
assert_cookie_present(target, "samesite_strict_lax", value)
];
if (isLegacySameSite()) {
// Legacy behavior: unsupported SameSite value acts like SameSite=None.
asserts.push(assert_cookie_present(target, "samesite_none_unsupported", value));
asserts.push(assert_cookie_present(target, "samesite_lax_unsupported", value));
asserts.push(assert_cookie_present(target, "samesite_strict_unsupported", value));
asserts.push(assert_cookie_present(target, "samesite_unsupported", value));
} else {
asserts.push(expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_none_unsupported", value) :
assert_cookie_present(target, "samesite_none_unsupported", value));
asserts.push(expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_lax_unsupported", value) :
assert_cookie_present(target, "samesite_lax_unsupported", value));
asserts.push(expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_strict_unsupported", value) :
assert_cookie_present(target, "samesite_strict_unsupported", value));
asserts.push(expectedStatus == SameSiteStatus.CROSS_SITE ?
assert_cookie_absent(target, "samesite_unsupported", value) :
assert_cookie_present(target, "samesite_unsupported", value));
}
return Promise.all(asserts);
});
}, title);
}
// No redirect:
create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site");
create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site");
create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site");
// Redirect from {same-host,subdomain,cross-site} to same-host:
create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site");
create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site");
create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host images are strictly same-site");
// Redirect from {same-host,subdomain,cross-site} to same-host:
create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site");
create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site");
create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain images are strictly same-site");
// Redirect from {same-host,subdomain,cross-site} to cross-site:
create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site");
create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site");
create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site");
</script>