gecko-dev/netwerk/cookie/CookieServiceParent.cpp
Nicholas Hurley c5f9b83562 Bug 1440462 - Send httponly cookie names to content processes. r=jdm
Previously, if script tried to set a cookie that matched a cookie we had
received via Set-Cookie that was labeled httponly, script would think
that cookie was properly set (even though it wasn't). This ensures that
script knows just enough about httponly cookies to prevent this
inconsistent view while avoiding leakages of the potentially-sensitive
cookie values.

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

--HG--
extra : moz-landing-system : lando
2018-09-26 15:39:33 +00:00

296 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/CookieServiceParent.h"
#include "mozilla/dom/PContentParent.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ipc/URIUtils.h"
#include "nsArrayUtils.h"
#include "nsCookieService.h"
#include "nsIChannel.h"
#include "nsIEffectiveTLDService.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPrivateBrowsingChannel.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
using namespace mozilla::ipc;
using mozilla::BasePrincipal;
using mozilla::OriginAttributes;
using mozilla::dom::PContentParent;
using mozilla::net::NeckoParent;
namespace {
// Ignore failures from this function, as they only affect whether we do or
// don't show a dialog box in private browsing mode if the user sets a pref.
void
CreateDummyChannel(nsIURI* aHostURI, nsIURI* aChannelURI,
OriginAttributes& aAttrs, nsIChannel** aChannel)
{
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateCodebasePrincipal(aHostURI, aAttrs);
if (!principal) {
return;
}
// The following channel is never openend, so it does not matter what
// securityFlags we pass; let's follow the principle of least privilege.
nsCOMPtr<nsIChannel> dummyChannel;
NS_NewChannel(getter_AddRefs(dummyChannel), aChannelURI, principal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INVALID);
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(dummyChannel);
if (!pbChannel) {
return;
}
pbChannel->SetPrivate(aAttrs.mPrivateBrowsingId > 0);
dummyChannel.forget(aChannel);
}
}
namespace mozilla {
namespace net {
CookieServiceParent::CookieServiceParent()
{
// Instantiate the cookieservice via the service manager, so it sticks around
// until shutdown.
nsCOMPtr<nsICookieService> cs = do_GetService(NS_COOKIESERVICE_CONTRACTID);
// Get the nsCookieService instance directly, so we can call internal methods.
mCookieService = nsCookieService::GetSingleton();
NS_ASSERTION(mCookieService, "couldn't get nsICookieService");
mProcessingCookie = false;
}
void
GetInfoFromCookie(nsCookie *aCookie,
CookieStruct &aCookieStruct)
{
aCookieStruct.name() = aCookie->Name();
aCookieStruct.value() = aCookie->Value();
aCookieStruct.host() = aCookie->Host();
aCookieStruct.path() = aCookie->Path();
aCookieStruct.expiry() = aCookie->Expiry();
aCookieStruct.lastAccessed() = aCookie->LastAccessed();
aCookieStruct.creationTime() = aCookie->CreationTime();
aCookieStruct.isSession() = aCookie->IsSession();
aCookieStruct.isSecure() = aCookie->IsSecure();
aCookieStruct.isHttpOnly() = aCookie->IsHttpOnly();
aCookieStruct.sameSite() = aCookie->SameSite();
}
void
CookieServiceParent::RemoveBatchDeletedCookies(nsIArray *aCookieList) {
uint32_t len = 0;
aCookieList->GetLength(&len);
OriginAttributes attrs;
CookieStruct cookieStruct;
nsTArray<CookieStruct> cookieStructList;
nsTArray<OriginAttributes> attrsList;
for (uint32_t i = 0; i < len; i++) {
nsCOMPtr<nsICookie> xpcCookie = do_QueryElementAt(aCookieList, i);
auto cookie = static_cast<nsCookie*>(xpcCookie.get());
attrs = cookie->OriginAttributesRef();
GetInfoFromCookie(cookie, cookieStruct);
if (cookie->IsHttpOnly()) {
// Child only needs to exist if an HttpOnly cookie exists, not its value
cookieStruct.value() = "";
}
cookieStructList.AppendElement(cookieStruct);
attrsList.AppendElement(attrs);
}
Unused << SendRemoveBatchDeletedCookies(cookieStructList, attrsList);
}
void
CookieServiceParent::RemoveAll()
{
Unused << SendRemoveAll();
}
void
CookieServiceParent::RemoveCookie(nsICookie *aCookie)
{
auto cookie = static_cast<nsCookie*>(aCookie);
OriginAttributes attrs = cookie->OriginAttributesRef();
CookieStruct cookieStruct;
GetInfoFromCookie(cookie, cookieStruct);
if (cookie->IsHttpOnly()) {
cookieStruct.value() = "";
}
Unused << SendRemoveCookie(cookieStruct, attrs);
}
void
CookieServiceParent::AddCookie(nsICookie *aCookie)
{
auto cookie = static_cast<nsCookie*>(aCookie);
OriginAttributes attrs = cookie->OriginAttributesRef();
CookieStruct cookieStruct;
GetInfoFromCookie(cookie, cookieStruct);
if (cookie->IsHttpOnly()) {
cookieStruct.value() = "";
}
Unused << SendAddCookie(cookieStruct, attrs);
}
void
CookieServiceParent::TrackCookieLoad(nsIChannel *aChannel)
{
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
mozilla::OriginAttributes attrs;
if (loadInfo) {
attrs = loadInfo->GetOriginAttributes();
}
bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel);
bool aIsSameSiteForeign = NS_IsSameSiteForeign(aChannel, uri);
// Send matching cookies to Child.
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil;
thirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
bool isForeign = true;
thirdPartyUtil->IsThirdPartyChannel(aChannel, uri, &isForeign);
bool isTrackingResource = false;
bool storageAccessGranted = false;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
if (httpChannel) {
isTrackingResource = httpChannel->GetIsTrackingResource();
// Check first-party storage access even for non-tracking resources, since
// we will need the result when computing the access rights for the reject
// foreign cookie behavior mode.
if (isForeign &&
AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(httpChannel,
uri,
nullptr)) {
storageAccessGranted = true;
}
}
nsTArray<nsCookie*> foundCookieList;
mCookieService->GetCookiesForURI(uri, isForeign, isTrackingResource,
storageAccessGranted, isSafeTopLevelNav,
aIsSameSiteForeign, false, attrs,
foundCookieList);
nsTArray<CookieStruct> matchingCookiesList;
SerialializeCookieList(foundCookieList, matchingCookiesList, uri);
Unused << SendTrackCookiesLoad(matchingCookiesList, attrs);
}
void
CookieServiceParent::SerialializeCookieList(const nsTArray<nsCookie*> &aFoundCookieList,
nsTArray<CookieStruct> &aCookiesList,
nsIURI *aHostURI)
{
for (uint32_t i = 0; i < aFoundCookieList.Length(); i++) {
nsCookie *cookie = aFoundCookieList.ElementAt(i);
CookieStruct* cookieStruct = aCookiesList.AppendElement();
cookieStruct->name() = cookie->Name();
if (!cookie->IsHttpOnly()) {
cookieStruct->value() = cookie->Value();
}
cookieStruct->host() = cookie->Host();
cookieStruct->path() = cookie->Path();
cookieStruct->expiry() = cookie->Expiry();
cookieStruct->lastAccessed() = cookie->LastAccessed();
cookieStruct->creationTime() = cookie->CreationTime();
cookieStruct->isSession() = cookie->IsSession();
cookieStruct->isSecure() = cookie->IsSecure();
cookieStruct->sameSite() = cookie->SameSite();
}
}
mozilla::ipc::IPCResult
CookieServiceParent::RecvPrepareCookieList(const URIParams &aHost,
const bool &aIsForeign,
const bool &aIsTrackingResource,
const bool &aFirstPartyStorageAccessGranted,
const bool &aIsSafeTopLevelNav,
const bool &aIsSameSiteForeign,
const OriginAttributes &aAttrs)
{
nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
// Send matching cookies to Child.
nsTArray<nsCookie*> foundCookieList;
mCookieService->GetCookiesForURI(hostURI, aIsForeign, aIsTrackingResource,
aFirstPartyStorageAccessGranted, aIsSafeTopLevelNav,
aIsSameSiteForeign, false, aAttrs,
foundCookieList);
nsTArray<CookieStruct> matchingCookiesList;
SerialializeCookieList(foundCookieList, matchingCookiesList, hostURI);
Unused << SendTrackCookiesLoad(matchingCookiesList, aAttrs);
return IPC_OK();
}
void
CookieServiceParent::ActorDestroy(ActorDestroyReason aWhy)
{
// Nothing needed here. Called right before destructor since this is a
// non-refcounted class.
}
mozilla::ipc::IPCResult
CookieServiceParent::RecvSetCookieString(const URIParams& aHost,
const OptionalURIParams& aChannelURI,
const bool& aIsForeign,
const bool& aIsTrackingResource,
const bool& aFirstPartyStorageAccessGranted,
const nsCString& aCookieString,
const nsCString& aServerTime,
const OriginAttributes& aAttrs,
const bool& aFromHttp)
{
if (!mCookieService)
return IPC_OK();
// Deserialize URI. Having a host URI is mandatory and should always be
// provided by the child; thus we consider failure fatal.
nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
if (!hostURI)
return IPC_FAIL_NO_REASON(this);
nsCOMPtr<nsIURI> channelURI = DeserializeURI(aChannelURI);
// This is a gross hack. We've already computed everything we need to know
// for whether to set this cookie or not, but we need to communicate all of
// this information through to nsICookiePermission, which indirectly
// computes the information from the channel. We only care about the
// aIsPrivate argument as nsCookieService::SetCookieStringInternal deals
// with aIsForeign before we have to worry about nsCookiePermission trying
// to use the channel to inspect it.
nsCOMPtr<nsIChannel> dummyChannel;
CreateDummyChannel(hostURI, channelURI,
const_cast<OriginAttributes&>(aAttrs),
getter_AddRefs(dummyChannel));
// NB: dummyChannel could be null if something failed in CreateDummyChannel.
nsDependentCString cookieString(aCookieString, 0);
// We set this to true while processing this cookie update, to make sure
// we don't send it back to the same content process.
mProcessingCookie = true;
mCookieService->SetCookieStringInternal(hostURI, aIsForeign,
aIsTrackingResource,
aFirstPartyStorageAccessGranted,
cookieString, aServerTime, aFromHttp,
aAttrs, dummyChannel);
mProcessingCookie = false;
return IPC_OK();
}
} // namespace net
} // namespace mozilla