diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
commit | 59203c63bb777a3bacec32fb8830fba33540e809 (patch) | |
tree | 58298e711c0ff0575818c30485b44a2f21bf28a0 /netwerk/cookie/CookieService.cpp | |
parent | Adding upstream version 126.0.1. (diff) | |
download | firefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip |
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | netwerk/cookie/CookieService.cpp | 207 |
1 files changed, 146 insertions, 61 deletions
diff --git a/netwerk/cookie/CookieService.cpp b/netwerk/cookie/CookieService.cpp index 0e90b8cbae..78698d44a0 100644 --- a/netwerk/cookie/CookieService.cpp +++ b/netwerk/cookie/CookieService.cpp @@ -373,15 +373,6 @@ CookieService::GetCookieStringFromDocument(Document* aDocument, return NS_OK; } - nsCOMPtr<nsIPrincipal> cookiePrincipal = - aDocument->EffectiveCookiePrincipal(); - - // TODO (Bug 1874174): A document could access both unpartitioned and - // partitioned cookie jars. We will need to prepare partitioned and - // unpartitioned principals for access both cookie jars. - nsTArray<nsCOMPtr<nsIPrincipal>> principals; - principals.AppendElement(cookiePrincipal); - bool thirdParty = true; nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow(); // in gtests we don't have a window, let's consider those requests as 3rd @@ -395,6 +386,26 @@ CookieService::GetCookieStringFromDocument(Document* aDocument, } } + nsCOMPtr<nsIPrincipal> cookiePrincipal = + aDocument->EffectiveCookiePrincipal(); + + nsTArray<nsCOMPtr<nsIPrincipal>> principals; + principals.AppendElement(cookiePrincipal); + + // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available + // (and therefore the partitioned principal), the unpartitioned cookie jar is + // only available in first-party or third-party with storageAccess contexts. + bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning(); + bool isUnpartitioned = + cookiePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty(); + if (isCHIPS && isUnpartitioned) { + // Assert that we are only doing this if we are first-party or third-party + // with storageAccess. + MOZ_ASSERT(!thirdParty || aDocument->UsingStorageAccess()); + // Add the partitioned principal to principals + principals.AppendElement(aDocument->PartitionedPrincipal()); + } + nsTArray<Cookie*> cookieList; for (auto& principal : principals) { @@ -519,20 +530,40 @@ CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel, ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel( aChannel, false, aHostURI, nullptr, &rejectedReason); - OriginAttributes attrs; - StoragePrincipalHelper::GetOriginAttributes( - aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal); - bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel); bool hadCrossSiteRedirects = false; bool isSameSiteForeign = CookieCommons::IsSameSiteForeign( aChannel, aHostURI, &hadCrossSiteRedirects); - // TODO (Bug 1874174): A channel could load both unpartitioned and partitioned - // cookie jars together. We will need to get cookies from both unpartitioned - // and partitioned cookie jars according to storage access. + OriginAttributes storageOriginAttributes; + StoragePrincipalHelper::GetOriginAttributes( + aChannel, storageOriginAttributes, + StoragePrincipalHelper::eStorageAccessPrincipal); + nsTArray<OriginAttributes> originAttributesList; - originAttributesList.AppendElement(attrs); + originAttributesList.AppendElement(storageOriginAttributes); + + // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available + // (and therefore the partitioned OriginAttributes), the unpartitioned cookie + // jar is only available in first-party or third-party with storageAccess + // contexts. + bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning(); + bool isUnpartitioned = storageOriginAttributes.mPartitionKey.IsEmpty(); + if (isCHIPS && isUnpartitioned) { + // Assert that we are only doing this if we are first-party or third-party + // with storageAccess. + MOZ_ASSERT( + !result.contains(ThirdPartyAnalysis::IsForeign) || + result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted)); + // Add the partitioned principal to principals + OriginAttributes partitionedOriginAttributes; + StoragePrincipalHelper::GetOriginAttributes( + aChannel, partitionedOriginAttributes, + StoragePrincipalHelper::ePartitionedPrincipal); + originAttributesList.AppendElement(partitionedOriginAttributes); + // Assert partitionedOAs have partitioneKey set. + MOZ_ASSERT(!partitionedOriginAttributes.mPartitionKey.IsEmpty()); + } AutoTArray<Cookie*, 8> foundCookieList; GetCookiesForURI( @@ -630,9 +661,10 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel( aChannel, false, aHostURI, nullptr, &rejectedReason); - OriginAttributes attrs; + OriginAttributes storagePrincipalOriginAttributes; StoragePrincipalHelper::GetOriginAttributes( - aChannel, attrs, StoragePrincipalHelper::eStorageAccessPrincipal); + aChannel, storagePrincipalOriginAttributes, + StoragePrincipalHelper::eStorageAccessPrincipal); // get the base domain for the host URI. // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". @@ -660,11 +692,11 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, baseDomainFromURI); NS_ENSURE_SUCCESS(rv, NS_OK); - CookieStorage* storage = PickStorage(attrs); + CookieStorage* storage = PickStorage(storagePrincipalOriginAttributes); // check default prefs uint32_t priorCookieCount = storage->CountCookiesFromHost( - baseDomainFromURI, attrs.mPrivateBrowsingId); + baseDomainFromURI, storagePrincipalOriginAttributes.mPrivateBrowsingId); nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel); @@ -674,7 +706,8 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), - aCookieHeader, priorCookieCount, attrs, &rejectedReason); + aCookieHeader, priorCookieCount, storagePrincipalOriginAttributes, + &rejectedReason); MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED); @@ -718,9 +751,24 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, nsCString cookieHeader(aCookieHeader); - bool moreCookieToRead = true; + // CHIPS - The partitioned cookie jar is always available and it is always + // possible to store cookies in it using the "Partitioned" attribute. + // Prepare the partitioned principals OAs to enable possible partitioned + // cookie storing from first-party or with StorageAccess. + // Similar behavior to CookieServiceChild::SetCookieStringFromHttp(). + OriginAttributes partitionedPrincipalOriginAttributes; + bool isPartitionedPrincipal = + !storagePrincipalOriginAttributes.mPartitionKey.IsEmpty(); + bool isCHIPS = StaticPrefs::network_cookie_cookieBehavior_optInPartitioning(); + // Only need to get OAs if we don't already use the partitioned principal. + if (isCHIPS && !isPartitionedPrincipal) { + StoragePrincipalHelper::GetOriginAttributes( + aChannel, partitionedPrincipalOriginAttributes, + StoragePrincipalHelper::ePartitionedPrincipal); + } // process each cookie in the header + bool moreCookieToRead = true; while (moreCookieToRead) { CookieStruct cookieData; bool canSetCookie = false; @@ -751,8 +799,22 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, continue; } + // CHIPS - If the partitioned attribute is set, store cookie in partitioned + // cookie jar independent of context. If the cookies are stored in the + // partitioned cookie jar anyway no special treatment of CHIPS cookies + // necessary. + bool needPartitioned = + isCHIPS && cookieData.isPartitioned() && !isPartitionedPrincipal; + OriginAttributes& cookieOriginAttributes = + needPartitioned ? partitionedPrincipalOriginAttributes + : storagePrincipalOriginAttributes; + // Assert that partitionedPrincipalOriginAttributes are initialized if used. + MOZ_ASSERT_IF( + needPartitioned, + !partitionedPrincipalOriginAttributes.mPartitionKey.IsEmpty()); + // create a new Cookie - RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs); + RefPtr<Cookie> cookie = Cookie::Create(cookieData, cookieOriginAttributes); MOZ_ASSERT(cookie); int64_t currentTimeInUsec = PR_Now(); @@ -763,8 +825,8 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, RefPtr<BrowsingContext> bc = loadInfo->GetBrowsingContext(); // add the cookie to the list. AddCookie() takes care of logging. - storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec, - aHostURI, aCookieHeader, true, bc); + storage->AddCookie(crc, baseDomain, cookieOriginAttributes, cookie, + currentTimeInUsec, aHostURI, aCookieHeader, true, bc); } return NS_OK; @@ -1180,6 +1242,16 @@ static void RecordPartitionedTelemetry(const CookieStruct& aCookieData, } } +static bool HasSecurePrefix(const nsACString& aString) { + return StringBeginsWith(aString, "__Secure-"_ns, + nsCaseInsensitiveCStringComparator); +} + +static bool HasHostPrefix(const nsACString& aString) { + return StringBeginsWith(aString, "__Host-"_ns, + nsCaseInsensitiveCStringComparator); +} + // processes a single cookie, and returns true if there are more cookies // to be processed bool CookieService::CanSetCookie( @@ -1290,9 +1362,12 @@ bool CookieService::CanSetCookie( return newCookie; } - if (!CheckHiddenPrefix(aCookieData)) { + // If a cookie is nameless, then its value must not start with + // `__Host-` or `__Secure-` + if (aCookieData.name().IsEmpty() && (HasSecurePrefix(aCookieData.value()) || + HasHostPrefix(aCookieData.value()))) { COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, - "failed the CheckHiddenPrefix tests"); + "failed hidden prefix tests"); CookieLogging::LogMessageToConsole( aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY, "CookieRejectedInvalidPrefix"_ns, @@ -1449,10 +1524,9 @@ bool CookieService::CanSetCookie( separators = ";" | "=" value-sep = ";" cookie-sep = CR | LF - allowed-chars = <any OCTET except NUL or cookie-sep> + allowed-chars = <any OCTET except cookie-sep> OCTET = <any 8-bit sequence of data> LWS = SP | HT - NUL = <US-ASCII NUL, null control character (0)> CR = <US-ASCII CR, carriage return (13)> LF = <US-ASCII LF, linefeed (10)> SP = <US-ASCII SP, space (32)> @@ -1472,6 +1546,8 @@ bool CookieService::CanSetCookie( | "Max-Age" "=" value | "Comment" "=" value | "Version" "=" value + | "Partitioned" + | "SameSite" | "Secure" | "HttpOnly" @@ -1479,7 +1555,6 @@ bool CookieService::CanSetCookie( // clang-format on // helper functions for GetTokenValue -static inline bool isnull(char c) { return c == 0; } static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; } static inline bool isterminator(char c) { return c == '\n' || c == '\r'; } static inline bool isvalueseparator(char c) { @@ -1507,7 +1582,7 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter, ++aIter; } start = aIter; - while (aIter != aEndIter && !isnull(*aIter) && !istokenseparator(*aIter)) { + while (aIter != aEndIter && !istokenseparator(*aIter)) { ++aIter; } @@ -1530,7 +1605,7 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter, // process <token> // just look for ';' to terminate ('=' allowed) - while (aIter != aEndIter && !isnull(*aIter) && !isvalueseparator(*aIter)) { + while (aIter != aEndIter && !isvalueseparator(*aIter)) { ++aIter; } @@ -1549,6 +1624,21 @@ bool CookieService::GetTokenValue(nsACString::const_char_iterator& aIter, // if on terminator, increment past & return true to process new cookie if (isterminator(*aIter)) { ++aIter; + while (aIter != aEndIter && isvalueseparator(*aIter)) { + ++aIter; + } + nsACString::const_char_iterator end = aIter - 1; + if (!isterminator(*end)) { + // The cookie isn't valid because we have multiple terminators or + // a terminator followed by a value separator. Add those invalid + // characters to the cookie string or value so it will be rejected. + if (aEqualsFound) { + aTokenString.Rebind(start, end); + } else { + aTokenValue.Rebind(start, end); + } + return false; + } return true; } // fall-through: aIter is on ';', increment and return false @@ -1570,6 +1660,17 @@ static inline void SetSameSiteAttribute(CookieStruct& aCookieData, aCookieData.rawSameSite() = aValue; } +// Tests for control characters, defined by RFC 5234 to be %x00-1F / %x7F. +// An exception is made for HTAB as the cookie spec treats that as whitespace. +static bool ContainsControlChars(const nsACString& aString) { + const auto* start = aString.BeginReading(); + const auto* end = aString.EndReading(); + + return std::find_if(start, end, [](unsigned char c) { + return (c <= 0x1F && c != 0x09) || c == 0x7F; + }) != end; +} + // 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. @@ -1627,6 +1728,14 @@ bool CookieService::ParseAttributes(nsIConsoleReportCollector* aCRC, newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); + if (ContainsControlChars(tokenString) || ContainsControlChars(tokenValue)) { + CookieLogging::LogMessageToConsole( + aCRC, aHostURI, nsIScriptError::errorFlag, CONSOLE_REJECTION_CATEGORY, + "CookieRejectedInvalidCharAttributes"_ns, + AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())}); + return newCookie; + } + // decide which attribute we have, and copy the string if (tokenString.LowerCaseEqualsLiteral(kPath)) { aCookieData.path() = tokenValue; @@ -1967,25 +2076,6 @@ bool CookieService::CheckDomain(CookieStruct& aCookieData, nsIURI* aHostURI, return true; } -// static -bool CookieService::CheckHiddenPrefix(CookieStruct& aCookieData) { - // If a cookie is nameless, then its value must not start with - // `__Host-` or `__Secure-` - if (!aCookieData.name().IsEmpty()) { - return true; - } - - if (StringBeginsWith(aCookieData.value(), "__Host-"_ns)) { - return false; - } - - if (StringBeginsWith(aCookieData.value(), "__Secure-"_ns)) { - return false; - } - - return true; -} - namespace { nsAutoCString GetPathFromURI(nsIURI* aHostURI) { // strip down everything after the last slash to get the path, @@ -2053,15 +2143,10 @@ bool CookieService::CheckPath(CookieStruct& aCookieData, // regularized and validated the CookieStruct values! bool CookieService::CheckPrefixes(CookieStruct& aCookieData, bool aSecureRequest) { - static const char kSecure[] = "__Secure-"; - static const char kHost[] = "__Host-"; - static const int kSecureLen = sizeof(kSecure) - 1; - static const int kHostLen = sizeof(kHost) - 1; - - bool isSecure = strncmp(aCookieData.name().get(), kSecure, kSecureLen) == 0; - bool isHost = strncmp(aCookieData.name().get(), kHost, kHostLen) == 0; + bool hasSecurePrefix = HasSecurePrefix(aCookieData.name()); + bool hasHostPrefix = HasHostPrefix(aCookieData.name()); - if (!isSecure && !isHost) { + if (!hasSecurePrefix && !hasHostPrefix) { // not one of the magic prefixes: carry on return true; } @@ -2072,7 +2157,7 @@ bool CookieService::CheckPrefixes(CookieStruct& aCookieData, return false; } - if (isHost) { + if (hasHostPrefix) { // The host prefix requires that the path is "/" and that the cookie // had no domain attribute. CheckDomain() and CheckPath() MUST be run // first to make sure invalid attributes are rejected and to regularlize |