Bug 1848406 - Detect stateful bounces for window associated storages. r=bvandersloot,asuth

Differential Revision: https://phabricator.services.mozilla.com/D203607
This commit is contained in:
Paul Zuehlcke 2024-04-17 20:29:04 +00:00
parent 68be9069f7
commit ba487d8c6e
10 changed files with 167 additions and 19 deletions

View file

@ -170,7 +170,10 @@ void BrowsingContextWebProgress::ContextReplaced(
already_AddRefed<BounceTrackingState>
BrowsingContextWebProgress::GetBounceTrackingState() {
if (!mBounceTrackingState) {
mBounceTrackingState = BounceTrackingState::GetOrCreate(this);
nsresult rv = NS_OK;
mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Failed to get BounceTrackingState.");
}
return do_AddRef(mBounceTrackingState);
}

View file

@ -48,6 +48,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/CallState.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DOMEventTargetHelper.h"
@ -4648,6 +4649,19 @@ already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyleHelper(
aError, nullptr);
}
void nsGlobalWindowInner::MaybeNotifyStorageKeyUsed() {
// Only notify once per window lifetime.
if (hasNotifiedStorageKeyUsed) {
return;
}
nsresult rv =
BounceTrackingStorageObserver::OnInitialStorageAccess(GetWindowContext());
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
hasNotifiedStorageKeyUsed = true;
}
Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
nsIPrincipal* principal = GetPrincipal();
nsIPrincipal* storagePrincipal;
@ -4770,6 +4784,8 @@ Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
}
}
MaybeNotifyStorageKeyUsed();
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p returns %p sessionStorage", this,
mSessionStorage.get()));
@ -4938,6 +4954,8 @@ Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
new PartitionedLocalStorage(this, principal, storagePrincipal, cache);
}
MaybeNotifyStorageKeyUsed();
MOZ_ASSERT(mLocalStorage);
MOZ_ASSERT(
mLocalStorage->Type() ==
@ -4957,6 +4975,8 @@ IDBFactory* nsGlobalWindowInner::GetIndexedDB(JSContext* aCx,
}
}
MaybeNotifyStorageKeyUsed();
return mIndexedDB;
}

View file

@ -728,6 +728,9 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::ErrorResult& aError);
void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
mozilla::ErrorResult& aError);
void MaybeNotifyStorageKeyUsed();
mozilla::dom::Storage* GetSessionStorage(mozilla::ErrorResult& aError);
mozilla::dom::Storage* GetLocalStorage(mozilla::ErrorResult& aError);
mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
@ -1389,6 +1392,11 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::Maybe<mozilla::StorageAccess> mStorageAllowedCache;
uint32_t mStorageAllowedReasonCache;
// When window associated storage is accessed we need to notify the parent
// process. This flag is used to ensure we only do it once per window
// lifetime.
bool hasNotifiedStorageKeyUsed{false};
RefPtr<mozilla::dom::DebuggerNotificationManager>
mDebuggerNotificationManager;

View file

@ -219,6 +219,10 @@ parent:
bool fromHttp,
CookieStruct[] cookies);
// Notify parent of storage access in the content process. This only happens
// once per window lifetime to avoid redundant IPC.
async OnInitialStorageAccess();
child:
async NotifyPermissionChange(nsCString type, uint32_t permission);
};

View file

@ -10,6 +10,7 @@
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/dom/InProcessParent.h"
@ -1705,6 +1706,13 @@ IPCResult WindowGlobalParent::RecvSetCookies(
aCookies, GetBrowsingContext());
}
IPCResult WindowGlobalParent::RecvOnInitialStorageAccess() {
DebugOnly<nsresult> rv =
BounceTrackingStorageObserver::OnInitialStorageAccess(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to notify storage access");
return IPC_OK();
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowContext,
mPageUseCountersWindow)

View file

@ -329,6 +329,8 @@ class WindowGlobalParent final : public WindowContext,
const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies);
mozilla::ipc::IPCResult RecvOnInitialStorageAccess();
private:
WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext,
uint64_t aInnerWindowId, uint64_t aOuterWindowId,

View file

@ -29,6 +29,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "nsTHashMap.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/WindowContext.h"
namespace mozilla {
@ -54,8 +55,13 @@ BounceTrackingState::~BounceTrackingState() {
// static
already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
dom::BrowsingContextWebProgress* aWebProgress) {
MOZ_ASSERT(aWebProgress);
dom::BrowsingContextWebProgress* aWebProgress, nsresult& aRv) {
aRv = NS_OK;
if (!aWebProgress) {
aRv = NS_ERROR_INVALID_ARG;
return nullptr;
}
if (!ShouldCreateBounceTrackingStateForWebProgress(aWebProgress)) {
return nullptr;
@ -73,8 +79,8 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
sStorageObserver = new BounceTrackingStorageObserver();
ClearOnShutdown(&sStorageObserver);
DebugOnly<nsresult> rv = sStorageObserver->Init();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to init storage observer");
aRv = sStorageObserver->Init();
NS_ENSURE_SUCCESS(aRv, nullptr);
}
dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext();
@ -90,10 +96,8 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
}));
if (createdNew) {
nsresult rv = bounceTrackingState->Init(aWebProgress);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
aRv = bounceTrackingState->Init(aWebProgress);
NS_ENSURE_SUCCESS(aRv, nullptr);
}
return bounceTrackingState.forget();
@ -614,4 +618,35 @@ nsresult BounceTrackingState::OnCookieWrite(const nsACString& aSiteHost) {
return NS_OK;
}
nsresult BounceTrackingState::OnStorageAccess(nsIPrincipal* aPrincipal) {
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_TRUE(aPrincipal->GetIsContentPrincipal(), NS_ERROR_FAILURE);
if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
nsAutoCString origin;
nsresult rv = aPrincipal->GetOrigin(origin);
if (NS_FAILED(rv)) {
origin = "err";
}
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: origin: %s, mBounceTrackingRecord: %s", __FUNCTION__,
origin.get(),
mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get()
: "null"));
}
if (!mBounceTrackingRecord) {
return NS_OK;
}
nsAutoCString siteHost;
nsresult rv = aPrincipal->GetBaseDomain(siteHost);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(!siteHost.IsEmpty(), NS_ERROR_FAILURE);
mBounceTrackingRecord->AddStorageAccessHost(siteHost);
return NS_OK;
}
} // namespace mozilla

View file

@ -45,7 +45,7 @@ class BounceTrackingState : public nsIWebProgressListener,
// return nullptr if the given web progress / browsing context is not suitable
// (see ShouldCreateBounceTrackingStateForWebProgress).
static already_AddRefed<BounceTrackingState> GetOrCreate(
dom::BrowsingContextWebProgress* aWebProgress);
dom::BrowsingContextWebProgress* aWebProgress, nsresult& aRv);
// Reset state for all BounceTrackingState instances this includes resetting
// BounceTrackingRecords and cancelling any running timers.
@ -99,6 +99,10 @@ class BounceTrackingState : public nsIWebProgressListener,
// Create a string that describes this object. Used for logging.
nsCString Describe();
// Record sites which have accessed storage in the current extended
// navigation.
nsresult OnStorageAccess(nsIPrincipal* aPrincipal);
private:
explicit BounceTrackingState();
virtual ~BounceTrackingState();
@ -140,12 +144,6 @@ class BounceTrackingState : public nsIWebProgressListener,
// final host.
nsresult OnDocumentLoaded(nsIPrincipal* aDocumentPrincipal);
// TODO: Bug 1839918: Detection of stateful bounces.
// Record sites which have accessed storage in the current extended
// navigation.
nsresult OnStorageAccess();
// Record sites which have activated service workers in the current
// extended navigation.
nsresult OnServiceWorkerActivation();

View file

@ -6,14 +6,15 @@
#include "BounceTrackingState.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsCOMPtr.h"
#include "nsICookieNotification.h"
#include "nsIObserverService.h"
#include "mozilla/dom/BrowsingContext.h"
#include "nsICookie.h"
#include "nsIPrincipal.h"
namespace mozilla {
@ -104,4 +105,66 @@ BounceTrackingStorageObserver::Observe(nsISupports* aSubject,
return bounceTrackingState->OnCookieWrite(baseDomain);
}
// static
nsresult BounceTrackingStorageObserver::OnInitialStorageAccess(
dom::WindowContext* aWindowContext) {
NS_ENSURE_ARG_POINTER(aWindowContext);
if (!XRE_IsParentProcess()) {
// Skip partitioned storage access. Checking this in the content process
// potentially saves us an IPC message to the parent.
nsIPrincipal* storagePrincipal =
aWindowContext->GetInnerWindow()->GetEffectiveStoragePrincipal();
if (storagePrincipal &&
!storagePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping partitioned storage access (content process)."));
return NS_OK;
}
dom::WindowGlobalChild* windowGlobalChild =
aWindowContext->GetWindowGlobalChild();
NS_ENSURE_TRUE(windowGlobalChild, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(windowGlobalChild->SendOnInitialStorageAccess(),
NS_ERROR_FAILURE);
return NS_OK;
}
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIPrincipal> storagePrincipal =
aWindowContext->Canonical()->DocumentStoragePrincipal();
NS_ENSURE_TRUE(storagePrincipal, NS_ERROR_FAILURE);
if (!storagePrincipal->GetIsContentPrincipal()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping non-content principal."));
return NS_OK;
}
if (!storagePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping partitioned storage access."));
return NS_OK;
}
dom::BrowsingContext* browsingContext = aWindowContext->GetBrowsingContext();
NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE);
nsresult rv = NS_OK;
RefPtr<BounceTrackingState> bounceTrackingState =
BounceTrackingState::GetOrCreate(
browsingContext->Top()->Canonical()->GetWebProgress(), rv);
NS_ENSURE_SUCCESS(rv, rv);
// We may not always get a BounceTrackingState, e.g. if the feature is
// disabled or we don't keep track of bounce tracking for the given
// BrowsingContext.
if (!bounceTrackingState) {
return NS_OK;
}
return bounceTrackingState->OnStorageAccess(storagePrincipal);
}
} // namespace mozilla

View file

@ -9,6 +9,10 @@
namespace mozilla {
namespace dom {
class WindowContext;
}
extern LazyLogModule gBounceTrackingProtectionLog;
class BounceTrackingStorageObserver final : public nsIObserver {
@ -19,6 +23,9 @@ class BounceTrackingStorageObserver final : public nsIObserver {
BounceTrackingStorageObserver() = default;
nsresult Init();
[[nodiscard]] static nsresult OnInitialStorageAccess(
dom::WindowContext* aWindowContext);
private:
~BounceTrackingStorageObserver() = default;
};