Bug 1902319 - Separate BTP user interaction tracking from ContentBlockingUserInteraction and record user interaction for iframes. r=anti-tracking-reviewers,manuel

Differential Revision: https://phabricator.services.mozilla.com/D214622
This commit is contained in:
Paul Zuehlcke 2024-06-30 12:39:58 +00:00
parent 98e9f35370
commit 2111e5b5ff
6 changed files with 95 additions and 26 deletions

View File

@ -46,6 +46,7 @@
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/Base64.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/BounceTrackingProtection.h"
#include "mozilla/CSSEnabledState.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/ContentBlockingNotifier.h"
@ -17127,6 +17128,17 @@ class UserInteractionTimer final : public Runnable,
// the document could be already gone.
mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
++userInteractionTimerId, aDocument);
// For ContentBlockingUserInteraction we care about user-interaction stored
// only for top-level documents and documents with access to the Storage
// Access API
if (aDocument->IsTopLevelContentDocument()) {
mShouldRecordContentBlockingUserInteraction = true;
} else {
bool hasSA;
nsresult rv = aDocument->HasStorageAccessSync(hasSA);
mShouldRecordContentBlockingUserInteraction = NS_SUCCEEDED(rv) && hasSA;
}
}
// Runnable interface
@ -17200,7 +17212,11 @@ class UserInteractionTimer final : public Runnable,
// If the document is not gone, let's reset its timer flag.
nsCOMPtr<Document> document(mDocument);
if (document) {
ContentBlockingUserInteraction::Observe(mPrincipal);
if (mShouldRecordContentBlockingUserInteraction) {
ContentBlockingUserInteraction::Observe(mPrincipal);
}
Unused << BounceTrackingProtection::RecordUserActivation(
mDocument->GetWindowContext());
document->ResetUserInteractionTimer();
}
}
@ -17227,6 +17243,7 @@ class UserInteractionTimer final : public Runnable,
nsCOMPtr<nsIPrincipal> mPrincipal;
WeakPtr<Document> mDocument;
bool mShouldRecordContentBlockingUserInteraction = false;
nsCOMPtr<nsITimer> mTimer;
@ -17239,18 +17256,21 @@ NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
} // namespace
void Document::MaybeStoreUserInteractionAsPermission() {
// We care about user-interaction stored only for top-level documents
// and documents with access to the Storage Access API
if (!IsTopLevelContentDocument()) {
bool hasSA;
nsresult rv = HasStorageAccessSync(hasSA);
if (NS_FAILED(rv) || !hasSA) {
return;
}
}
if (!mUserHasInteracted) {
// First interaction, let's store this info now.
Unused << BounceTrackingProtection::RecordUserActivation(
GetWindowContext());
// For ContentBlockingUserInteraction we care about user-interaction stored
// only for top-level documents and documents with access to the Storage
// Access API
if (!IsTopLevelContentDocument()) {
bool hasSA;
nsresult rv = HasStorageAccessSync(hasSA);
if (NS_FAILED(rv) || !hasSA) {
return;
}
}
ContentBlockingUserInteraction::Observe(NodePrincipal());
return;
}

View File

@ -13,6 +13,7 @@
#include "WebAuthnTransportIdentifiers.h"
#include "mozilla/Base64.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/BounceTrackingProtection.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
@ -23,7 +24,6 @@
#include "mozilla/dom/WebAuthnManager.h"
#include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/JSONStringWriteFuncs.h"
@ -844,8 +844,8 @@ void WebAuthnManager::FinishGetAssertion(
if (global) {
nsPIDOMWindowInner* window = global->GetAsInnerWindow();
if (window) {
WindowGlobalChild* windowGlobalChild = window->GetWindowGlobalChild();
windowGlobalChild->SendRecordUserActivationForBTP();
Unused << BounceTrackingProtection::RecordUserActivation(
window->GetWindowContext());
}
}

View File

@ -27,12 +27,6 @@ void ContentBlockingUserInteraction::Observe(nsIPrincipal* aPrincipal) {
if (XRE_IsParentProcess()) {
LOG_PRIN(("Saving the userInteraction for %s", _spec), aPrincipal);
// The bounce tracking protection has its own interaction store.
nsresult rv = BounceTrackingProtection::RecordUserActivation(aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("BounceTrackingProtection::RecordUserActivation failed."));
}
PermissionManager* permManager = PermissionManager::GetInstance();
if (NS_WARN_IF(!permManager)) {
LOG(("Permission manager is null, bailing out early"));
@ -46,7 +40,7 @@ void ContentBlockingUserInteraction::Observe(nsIPrincipal* aPrincipal) {
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
uint32_t privateBrowsingId = 0;
rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
// If we are coming from a private window, make sure to store a
// session-only permission which won't get persisted to disk.

View File

@ -9,6 +9,7 @@
#include "AntiTrackingUtils.h"
#include "TemporaryAccessGrantObserver.h"
#include "mozilla/BounceTrackingProtection.h"
#include "mozilla/Components.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/ContentBlockingUserInteraction.h"
@ -501,11 +502,14 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess(
LOG(("Saving the permission: trackingOrigin=%s", trackingOrigin.get()));
bool frameOnly = StaticPrefs::dom_storage_access_frame_only() &&
aReason == ContentBlockingNotifier::eStorageAccessAPI;
uint64_t innerWindowId = aParentContext->GetCurrentInnerWindowId();
return SaveAccessForOriginOnParentProcess(aTopLevelWindowId, aParentContext,
trackingPrincipal, aAllowMode,
frameOnly)
->Then(GetCurrentSerialEventTarget(), __func__,
[aReason, trackingPrincipal](
[aReason, trackingPrincipal, innerWindowId](
ParentAccessGrantPromise::ResolveOrRejectValue&& aValue) {
if (!aValue.IsResolve()) {
return StorageAccessPermissionGrantPromise::CreateAndReject(
@ -518,6 +522,12 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess(
// occur through the clicking accept on the doorhanger.
if (aReason == ContentBlockingNotifier::eStorageAccessAPI) {
ContentBlockingUserInteraction::Observe(trackingPrincipal);
RefPtr<dom::WindowContext> windowContext =
dom::WindowContext::GetById(innerWindowId);
if (windowContext) {
Unused << BounceTrackingProtection::RecordUserActivation(
windowContext);
}
}
return StorageAccessPermissionGrantPromise::CreateAndResolve(
StorageAccessAPIHelper::eAllow, __func__);
@ -637,20 +647,29 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnChildProcess(
// sending the request of storing a permission.
bool frameOnly = StaticPrefs::dom_storage_access_frame_only() &&
aReason == ContentBlockingNotifier::eStorageAccessAPI;
uint64_t innerWindowId = aParentContext->GetCurrentInnerWindowId();
return cc
->SendStorageAccessPermissionGrantedForOrigin(
aTopLevelWindowId, aParentContext, trackingPrincipal,
trackingOrigin, aAllowMode, reportReason, frameOnly)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aReason, trackingPrincipal](
const ContentChild::
StorageAccessPermissionGrantedForOriginPromise::
ResolveOrRejectValue& aValue) {
[aReason, trackingPrincipal,
innerWindowId](const ContentChild::
StorageAccessPermissionGrantedForOriginPromise::
ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
if (aValue.ResolveValue() &&
(aReason == ContentBlockingNotifier::eStorageAccessAPI)) {
ContentBlockingUserInteraction::Observe(trackingPrincipal);
RefPtr<dom::WindowContext> windowContext =
dom::WindowContext::GetById(innerWindowId);
if (windowContext) {
Unused << BounceTrackingProtection::RecordUserActivation(
windowContext);
}
}
return StorageAccessPermissionGrantPromise::CreateAndResolve(
aValue.ResolveValue(), __func__);

View File

@ -35,6 +35,9 @@
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/ContentBlockingLog.h"
#include "mozilla/glean/GleanPings.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#define TEST_OBSERVER_MSG_RECORD_BOUNCES_FINISHED "test-record-bounces-finished"
@ -319,6 +322,26 @@ nsresult BounceTrackingProtection::RecordUserActivation(
aActivationTime.valueOr(PR_Now()));
}
nsresult BounceTrackingProtection::RecordUserActivation(
dom::WindowContext* aWindowContext) {
NS_ENSURE_ARG_POINTER(aWindowContext);
if (XRE_IsContentProcess()) {
dom::WindowGlobalChild* wgc = aWindowContext->GetWindowGlobalChild();
NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(wgc->SendRecordUserActivationForBTP(), NS_ERROR_FAILURE);
return NS_OK;
}
MOZ_ASSERT(XRE_IsParentProcess());
dom::WindowGlobalParent* wgp = aWindowContext->Canonical();
MOZ_ASSERT(wgp);
NS_ENSURE_TRUE(wgp->RecvRecordUserActivationForBTP(), NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {

View File

@ -22,6 +22,10 @@ class BounceTrackingProtectionStorage;
class ClearDataCallback;
class OriginAttributes;
namespace dom {
class WindowContext;
}
using ClearDataMozPromise = MozPromise<nsCString, uint32_t, true>;
extern LazyLogModule gBounceTrackingProtectionLog;
@ -46,9 +50,18 @@ class BounceTrackingProtection final : public nsIObserver,
// Stores a user activation flag with a timestamp for the given principal. The
// timestamp defaults to the current time, but can be overridden via
// aActivationTime.
// Parent process only. Prefer the WindowContext variant if possible.
[[nodiscard]] static nsresult RecordUserActivation(
nsIPrincipal* aPrincipal, Maybe<PRTime> aActivationTime = Nothing());
// Same as above but can be called from any process given a WindowContext.
// Gecko callers should prefer this method because it takes care of IPC and
// gets the principal user activation. IPC messages from the content to parent
// passing a principal should be avoided for security reasons. aActivationTime
// defaults to PR_Now().
[[nodiscard]] static nsresult RecordUserActivation(
dom::WindowContext* aWindowContext);
// Clears expired user interaction flags for the given state global. If
// aStateGlobal == nullptr, clears expired user interaction flags for all
// state globals.