tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a59040a6b05d918cd8ed64f7fd7325a7115a6f44
parent a66fab0931d975204ebe2cf943bf6885c756acf1
Author: Tom Ritter <tom@mozilla.com>
Date:   Mon,  8 Dec 2025 16:31:01 +0000

Bug 1873716: Record (and separate activity) by source r=timhuang

SKIP_BMO_CHECK

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

Diffstat:
Mdom/base/Document.cpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdom/base/Document.h | 6++++--
Mdom/canvas/nsICanvasRenderingContextInternal.h | 1-
Mdom/html/HTMLCanvasElement.cpp | 20++++++++++----------
Mtoolkit/components/resistfingerprinting/nsRFPIPCUtils.h | 12++++++------
Mtoolkit/components/resistfingerprinting/nsRFPService.cpp | 372++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtoolkit/components/resistfingerprinting/nsRFPService.h | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
7 files changed, 539 insertions(+), 52 deletions(-)

diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -477,6 +477,7 @@ mozilla::LazyLogModule gPageCacheLog("PageCache"); mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache"); mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer"); mozilla::LazyLogModule gUseCountersLog("UseCounters"); +static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); namespace mozilla { @@ -17591,35 +17592,91 @@ bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const { void Document::RecordCanvasUsage(CanvasUsage& aUsage) { // Limit the number of recent canvas extraction uses that are tracked. - const size_t kTrackedCanvasLimit = 8; + // Because we are now covering more canvas extraction sources, we need to + // increase this a bit + const size_t kTrackedCanvasLimit = 15; // Timeout between different canvas extractions. const uint64_t kTimeoutUsec = 3000 * 1000; uint64_t now = PR_Now(); - if ((mCanvasUsage.Length() > kTrackedCanvasLimit) || - ((now - mLastCanvasUsage) > kTimeoutUsec)) { - mCanvasUsage.ClearAndRetainStorage(); - } - mCanvasUsage.AppendElement(aUsage); - mLastCanvasUsage = now; nsCString originNoSuffix; + nsCString uri; if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { - return; + MOZ_LOG(gFingerprinterDetection, LogLevel::Error, + ("Document:: %p Could not get originsuffix", this)); + return; + } + if (NS_FAILED(NodePrincipal()->GetSpec(uri))) { + MOZ_LOG(gFingerprinterDetection, LogLevel::Error, + ("Document:: %p Could not get uri", this)); + return; + } + if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Debug)) { + nsAutoCString filename; + uint32_t lineNum = 0; + filename.AssignLiteral("<unknown>"); + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (cx) { + JS::AutoFilename scriptFilename; + JS::ColumnNumberOneOrigin colOneOrigin; + if (JS::DescribeScriptedCaller(&scriptFilename, cx, &lineNum, + &colOneOrigin)) { + if (const char* file = scriptFilename.get()) { + filename = nsDependentCString(file); + } + } + } + + MOZ_LOG(gFingerprinterDetection, LogLevel::Debug, + ("Document:: %p %s recording canvas usage of type %s on %s in %s", + this, originNoSuffix.get(), + CanvasUsageSourceToString(aUsage.mUsageSource).get(), uri.get(), + filename.get())); } - nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(), + // Check if we need to clear the usage data for this source. + if (mCanvasUsageLastTimestamp != 0 && + (now - mCanvasUsageLastTimestamp) > kTimeoutUsec) { + MOZ_LOG( + gFingerprinterDetection, LogLevel::Verbose, + ("Document:: %p %s clearing canvas array", this, originNoSuffix.get())); + mCanvasUsageData.Clear(); + } else if (mCanvasUsageData.Length() > kTrackedCanvasLimit) { + MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, + ("Document:: %p %s removing oldest canvas " + "usage in array", + this, originNoSuffix.get())); + mCanvasUsageData.RemoveElementAt(0); + } else { + MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, + ("Document:: %p %s recorded canvas " + "usage of type %s in array, records: %zu", + this, originNoSuffix.get(), + CanvasUsageSourceToString(aUsage.mUsageSource).get(), + static_cast<size_t>(mCanvasUsageData.Length() + 1))); + } + + // Update the timestamp and append the new usage. + mCanvasUsageLastTimestamp = now; + mCanvasUsageData.AppendElement(aUsage); + + nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsageData, channel, uri, originNoSuffix); } void Document::RecordFontFingerprinting() { + nsCString uri; nsCString originNoSuffix; if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { return; } + if (NS_FAILED(NodePrincipal()->GetSpec(uri))) { + return; + } - nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix); + nsRFPService::MaybeReportFontFingerprinter(GetChannel(), uri, originNoSuffix); } bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; } diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -5744,8 +5744,10 @@ class Document : public nsINode, // Used for tracking a number of recent canvas extractions (e.g. toDataURL), // this is used for a canvas fingerprinter detection heuristic. - nsTArray<CanvasUsage> mCanvasUsage; - uint64_t mLastCanvasUsage = 0; + // Store all recent usages in a single nsTArray instead of a hash map. + nsTArray<CanvasUsage> mCanvasUsageData; + // Timestamp (PR_Now microseconds) of the last update to mCanvasUsageData. + uint64_t mCanvasUsageLastTimestamp = 0; RefPtr<class FragmentDirective> mFragmentDirective; UniquePtr<RadioGroupContainer> mRadioGroupContainer; diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -21,7 +21,6 @@ #include "nsIDocShell.h" #include "nsIInputStream.h" #include "nsISupports.h" -#include "nsRFPService.h" #include "nsRefreshObservers.h" #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \ diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp @@ -928,16 +928,9 @@ nsresult HTMLCanvasElement::ExtractData(JSContext* aCx, if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { auto size = GetWidthHeight(); - CanvasContextType type = GetCurrentContextType(); - CanvasFeatureUsage featureUsage = CanvasFeatureUsage::None; - if (type == CanvasContextType::Canvas2D) { - if (auto ctx = - static_cast<CanvasRenderingContext2D*>(GetCurrentContext())) { - featureUsage = ctx->FeatureUsage(); - } - } - - CanvasUsage usage(size, type, featureUsage); + auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(), + CanvasExtractionAPI::ToDataURL, size, + GetCurrentContext()); OwnerDoc()->RecordCanvasUsage(usage); } @@ -1097,6 +1090,13 @@ void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback, global, &aCallback, recheckCanRead ? mOffscreenDisplay.get() : nullptr, recheckCanRead ? &aSubjectPrincipal : nullptr); + auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(), + CanvasExtractionAPI::ToBlob, + GetWidthHeight(), GetCurrentContext()); + if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { + OwnerDoc()->RecordCanvasUsage(usage); + } + CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams, extractionBehaviour, aRv); } diff --git a/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h b/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h @@ -19,7 +19,7 @@ struct ParamTraits<mozilla::CanvasFingerprinterAlias> : public ContiguousEnumSerializerInclusive< mozilla::CanvasFingerprinterAlias, mozilla::CanvasFingerprinterAlias::eNoneIdentified, - mozilla::CanvasFingerprinterAlias::eMaybe> {}; + mozilla::CanvasFingerprinterAlias::eLastAlias> {}; // CanvasFingerprintingEvent template <> @@ -29,21 +29,21 @@ struct ParamTraits<mozilla::CanvasFingerprintingEvent> { static void Write(MessageWriter* aWriter, const paramType& aParam) { WriteParam(aWriter, aParam.alias); WriteParam(aWriter, aParam.knownTextBitmask); - WriteParam(aWriter, aParam.source); + WriteParam(aWriter, aParam.sourcesBitmask); } static bool Read(MessageReader* aReader, paramType* aResult) { mozilla::CanvasFingerprinterAlias alias; uint32_t knownTextBitmask; - uint8_t source; + uint64_t sourcesBitmask; if (!ReadParam(aReader, &alias) || !ReadParam(aReader, &knownTextBitmask) || - !ReadParam(aReader, &source)) { + !ReadParam(aReader, &sourcesBitmask)) { return false; } - *aResult = - mozilla::CanvasFingerprintingEvent(alias, knownTextBitmask, source); + *aResult = mozilla::CanvasFingerprintingEvent(alias, knownTextBitmask, + sourcesBitmask); return true; } }; diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp @@ -44,6 +44,7 @@ #include "mozilla/TextEvents.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/CanvasRenderingContextHelper.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" @@ -1951,6 +1952,340 @@ static const char* CanvasFingerprinterToString( return "<error>"; } +namespace mozilla { +nsCString CanvasUsageSourceToString(CanvasUsageSource aSource) { + if (aSource == CanvasUsageSource::Unknown) { + return "None"_ns; + } + + nsCString accumulated; + + auto append = [&](const char* name) { + if (!accumulated.IsEmpty()) { + accumulated.AppendLiteral(", "); + } + accumulated.Append(name); + }; + +#define APPEND_IF_SET(flag, name) \ + if ((aSource & (flag)) == (flag)) { \ + append(name); \ + } + + APPEND_IF_SET(Impossible, "Impossible"); + + APPEND_IF_SET(MainThread_Canvas_ImageBitmap_toDataURL, + "MainThread_Canvas_ImageBitmap_toDataURL"); + APPEND_IF_SET(MainThread_Canvas_ImageBitmap_toBlob, + "MainThread_Canvas_ImageBitmap_toBlob"); + APPEND_IF_SET(MainThread_Canvas_ImageBitmap_getImageData, + "MainThread_Canvas_ImageBitmap_getImageData"); + + APPEND_IF_SET(MainThread_Canvas_Canvas2D_toDataURL, + "MainThread_Canvas_Canvas2D_toDataURL"); + APPEND_IF_SET(MainThread_Canvas_Canvas2D_toBlob, + "MainThread_Canvas_Canvas2D_toBlob"); + APPEND_IF_SET(MainThread_Canvas_Canvas2D_getImageData, + "MainThread_Canvas_Canvas2D_getImageData"); + + APPEND_IF_SET(MainThread_Canvas_WebGL_toDataURL, + "MainThread_Canvas_WebGL_toDataURL"); + APPEND_IF_SET(MainThread_Canvas_WebGL_toBlob, + "MainThread_Canvas_WebGL_toBlob"); + APPEND_IF_SET(MainThread_Canvas_WebGL_getImageData, + "MainThread_Canvas_WebGL_getImageData"); + APPEND_IF_SET(MainThread_Canvas_WebGL_readPixels, + "MainThread_Canvas_WebGL_readPixels"); + + APPEND_IF_SET(MainThread_Canvas_WebGPU_toDataURL, + "MainThread_Canvas_WebGPU_toDataURL"); + APPEND_IF_SET(MainThread_Canvas_WebGPU_toBlob, + "MainThread_Canvas_WebGPU_toBlob"); + APPEND_IF_SET(MainThread_Canvas_WebGPU_getImageData, + "MainThread_Canvas_WebGPU_getImageData"); + + APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_toDataURL, + "MainThread_OffscreenCanvas_ImageBitmap_toDataURL"); + APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_toBlob, + "MainThread_OffscreenCanvas_ImageBitmap_toBlob"); + APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_getImageData, + "MainThread_OffscreenCanvas_ImageBitmap_getImageData"); + + APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_toDataURL, + "MainThread_OffscreenCanvas_Canvas2D_toDataURL"); + APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_toBlob, + "MainThread_OffscreenCanvas_Canvas2D_toBlob"); + APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_getImageData, + "MainThread_OffscreenCanvas_Canvas2D_getImageData"); + + APPEND_IF_SET(Worker_OffscreenCanvas_Canvas2D_toBlob, + "Worker_OffscreenCanvas_Canvas2D_toBlob"); + APPEND_IF_SET(Worker_OffscreenCanvas_Canvas2D_getImageData, + "Worker_OffscreenCanvas_Canvas2D_getImageData"); + + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_toDataURL, + "MainThread_OffscreenCanvas_WebGL_toDataURL"); + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_toBlob, + "MainThread_OffscreenCanvas_WebGL_toBlob"); + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_getImageData, + "MainThread_OffscreenCanvas_WebGL_getImageData"); + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_readPixels, + "MainThread_OffscreenCanvas_WebGL_readPixels"); + + APPEND_IF_SET(Worker_OffscreenCanvas_ImageBitmap_toBlob, + "Worker_OffscreenCanvas_ImageBitmap_toBlob"); + APPEND_IF_SET(Worker_OffscreenCanvas_ImageBitmap_getImageData, + "Worker_OffscreenCanvas_ImageBitmap_getImageData"); + + APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_toBlob, + "Worker_OffscreenCanvas_WebGL_toBlob"); + APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_getImageData, + "Worker_OffscreenCanvas_WebGL_getImageData"); + APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_readPixels, + "Worker_OffscreenCanvas_WebGL_readPixels"); + + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_toDataURL, + "MainThread_OffscreenCanvas_WebGPU_toDataURL"); + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_toBlob, + "MainThread_OffscreenCanvas_WebGPU_toBlob"); + APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_getImageData, + "MainThread_OffscreenCanvas_WebGPU_getImageData"); + + APPEND_IF_SET(Worker_OffscreenCanvas_WebGPU_toBlob, + "Worker_OffscreenCanvas_WebGPU_toBlob"); + APPEND_IF_SET(Worker_OffscreenCanvas_WebGPU_getImageData, + "Worker_OffscreenCanvas_WebGPU_getImageData"); + + APPEND_IF_SET(Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData, + "Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData"); + APPEND_IF_SET(Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob, + "Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob"); + +#undef APPEND_IF_SET + + if (accumulated.IsEmpty()) { + accumulated.AssignLiteral("<error>"); + } + + return accumulated; +} +} // namespace mozilla + +CanvasUsage CanvasUsage::CreateUsage( + bool aIsOffscreen, dom::CanvasContextType aContextType, + CanvasExtractionAPI aApi, CSSIntSize aSize, + const nsICanvasRenderingContextInternal* aContext) { + CanvasFeatureUsage featureUsage = CanvasFeatureUsage::None; + + // Only 2D contexts currently expose feature usage metrics. + if (aContextType == dom::CanvasContextType::Canvas2D && aContext) { + // Downcast is safe because 2D contexts derive from + // nsICanvasRenderingContextInternal and we gated on the supplied type. The + // header is included so the relationship is known to the compiler. + auto* ctx2D = static_cast<const dom::CanvasRenderingContext2D*>(aContext); + featureUsage = ctx2D->FeatureUsage(); + } + + CanvasUsageSource usageSource = + CanvasUsage::GetCanvasUsageSource(aIsOffscreen, aContextType, aApi); + return CanvasUsage(aSize, aContextType, usageSource, featureUsage); +} + +CanvasUsageSource CanvasUsage::GetCanvasUsageSource( + bool isOffscreen, dom::CanvasContextType contextType, + CanvasExtractionAPI api) { + const bool isMainThread = NS_IsMainThread(); + + auto logImpossible = [&](const char* aComment = "") { + MOZ_LOG(gFingerprinterDetection, LogLevel::Error, + ("CanvasUsageSource impossible: comment=%s isOffscreen=%d " + "contextType=%d api=%d isMainThread=%d", + aComment, isOffscreen, static_cast<int>(contextType), + static_cast<int>(api), isMainThread)); + }; + + // Non-offscreen (HTMLCanvasElement) path (always main thread). + if (!isOffscreen) { + if (!isMainThread) { + logImpossible("Non-offscreen canvas accessed off main thread"); + return CanvasUsageSource::Impossible; + } + switch (contextType) { + case dom::CanvasContextType::Canvas2D: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + return MainThread_Canvas_Canvas2D_toDataURL; + case CanvasExtractionAPI::ToBlob: + return MainThread_Canvas_Canvas2D_toBlob; + case CanvasExtractionAPI::GetImageData: + return MainThread_Canvas_Canvas2D_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for Canvas2D"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for Canvas2D"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::WebGL1: + case dom::CanvasContextType::WebGL2: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + return MainThread_Canvas_WebGL_toDataURL; + case CanvasExtractionAPI::ToBlob: + return MainThread_Canvas_WebGL_toBlob; + case CanvasExtractionAPI::GetImageData: + return MainThread_Canvas_WebGL_getImageData; + case CanvasExtractionAPI::ReadPixels: + return MainThread_Canvas_WebGL_readPixels; + default: + logImpossible("Unknown API for WebGL"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::WebGPU: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + return MainThread_Canvas_WebGPU_toDataURL; + case CanvasExtractionAPI::ToBlob: + return MainThread_Canvas_WebGPU_toBlob; + case CanvasExtractionAPI::GetImageData: + return MainThread_Canvas_WebGPU_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for WebGPU"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for WebGPU"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::ImageBitmap: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + return MainThread_Canvas_ImageBitmap_toDataURL; + case CanvasExtractionAPI::ToBlob: + return MainThread_Canvas_ImageBitmap_toBlob; + case CanvasExtractionAPI::GetImageData: + return MainThread_Canvas_ImageBitmap_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for ImageBitmap"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for ImageBitmap"); + return CanvasUsageSource::Impossible; + } + default: + logImpossible("Unknown context type (main thread, non-offscreen)"); + return CanvasUsageSource::Impossible; + } + } + + // OffscreenCanvas path. + switch (contextType) { + case dom::CanvasContextType::Canvas2D: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + if (isMainThread) { + return MainThread_OffscreenCanvas_Canvas2D_toDataURL; + } + logImpossible("ToDataURL invalid for Offscreen Canvas2D on worker"); + return CanvasUsageSource::Impossible; // not supported + case CanvasExtractionAPI::ToBlob: + return isMainThread ? MainThread_OffscreenCanvas_Canvas2D_toBlob + : Worker_OffscreenCanvas_Canvas2D_toBlob; + case CanvasExtractionAPI::GetImageData: + return isMainThread ? MainThread_OffscreenCanvas_Canvas2D_getImageData + : Worker_OffscreenCanvas_Canvas2D_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for Offscreen 2D"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for Offscreen Canvas2D"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::OffscreenCanvas2D: + if (isMainThread) { + logImpossible("OffscreenCanvas2D on main thread"); + return CanvasUsageSource::Impossible; + } + switch (api) { + case CanvasExtractionAPI::GetImageData: + return Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData; + case CanvasExtractionAPI::ToBlob: + return Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob; + default: + logImpossible("Unsupported API for OffscreenCanvas2D"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::WebGL1: + case dom::CanvasContextType::WebGL2: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + if (!isMainThread) { + logImpossible("ToDataURL invalid for Offscreen WebGL on worker"); + return CanvasUsageSource::Impossible; // not supported + } + return MainThread_OffscreenCanvas_WebGL_toDataURL; + case CanvasExtractionAPI::ToBlob: + return isMainThread ? MainThread_OffscreenCanvas_WebGL_toBlob + : Worker_OffscreenCanvas_WebGL_toBlob; + case CanvasExtractionAPI::GetImageData: + return isMainThread ? MainThread_OffscreenCanvas_WebGL_getImageData + : Worker_OffscreenCanvas_WebGL_getImageData; + case CanvasExtractionAPI::ReadPixels: + return isMainThread ? MainThread_OffscreenCanvas_WebGL_readPixels + : Worker_OffscreenCanvas_WebGL_readPixels; + default: + logImpossible("Unknown API for Offscreen WebGL"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::WebGPU: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + if (!isMainThread) { + logImpossible("ToDataURL invalid for Offscreen WebGPU on worker"); + return CanvasUsageSource::Impossible; // not supported + } + return MainThread_OffscreenCanvas_WebGPU_toDataURL; + case CanvasExtractionAPI::ToBlob: + return isMainThread ? MainThread_OffscreenCanvas_WebGPU_toBlob + : Worker_OffscreenCanvas_WebGPU_toBlob; + case CanvasExtractionAPI::GetImageData: + return isMainThread ? MainThread_OffscreenCanvas_WebGPU_getImageData + : Worker_OffscreenCanvas_WebGPU_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for Offscreen WebGPU"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for Offscreen WebGPU"); + return CanvasUsageSource::Impossible; + } + case dom::CanvasContextType::ImageBitmap: + switch (api) { + case CanvasExtractionAPI::ToDataURL: + if (!isMainThread) { + logImpossible( + "ToDataURL invalid for Offscreen ImageBitmap on worker"); + return CanvasUsageSource::Impossible; // not supported worker + } + return MainThread_OffscreenCanvas_ImageBitmap_toDataURL; + case CanvasExtractionAPI::ToBlob: + return isMainThread ? MainThread_OffscreenCanvas_ImageBitmap_toBlob + : Worker_OffscreenCanvas_ImageBitmap_toBlob; + case CanvasExtractionAPI::GetImageData: + return isMainThread + ? MainThread_OffscreenCanvas_ImageBitmap_getImageData + : Worker_OffscreenCanvas_ImageBitmap_getImageData; + case CanvasExtractionAPI::ReadPixels: + logImpossible("ReadPixels invalid for Offscreen ImageBitmap"); + return CanvasUsageSource::Impossible; + default: + logImpossible("Unknown API for Offscreen ImageBitmap"); + return CanvasUsageSource::Impossible; + } + default: + logImpossible("Unknown context type (offscreen)"); + return CanvasUsageSource::Impossible; + } +} + static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, uint32_t& aColumnNum) { aFilename.AssignLiteral("<unknown>"); @@ -1971,8 +2306,8 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, } /* static */ void nsRFPService::MaybeReportCanvasFingerprinter( - nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, - nsACString& aOriginNoSuffix) { + nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, const nsACString& aURI, + const nsACString& aOriginNoSuffix) { if (!aChannel) { return; } @@ -1987,6 +2322,7 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, bool seenExtracted2D_280x60 = false; bool seenExtracted2D_860x6 = false; CanvasFeatureUsage accumulatedFeatureUsage = CanvasFeatureUsage::None; + CanvasUsageSource accumulatedUsageSource = CanvasUsageSource::Unknown; uint32_t extractedOther = 0; @@ -1999,6 +2335,8 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, continue; } + accumulatedUsageSource |= usage.mUsageSource; + if (usage.mType == dom::CanvasContextType::Canvas2D) { accumulatedFeatureUsage |= usage.mFeatureUsage; extracted2D++; @@ -2069,19 +2407,22 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, MOZ_LOG( gFingerprinterDetection, LogLevel::Info, ("Detected a potential canvas fingerprinter on %s in script %s:%d:%d " - "(KnownFingerprintTextBitmask: %u, CanvasFingerprinterAlias: %s)", + "(KnownFingerprintTextBitmask: %u, CanvasFingerprinterAlias: %s, " + "CanvasUsageSource: %s)", origin.get(), filename.get(), lineNum, columnNum, knownTextBitmask, - CanvasFingerprinterToString(fingerprinter))); + CanvasFingerprinterToString(fingerprinter), + CanvasUsageSourceToString(aSource))); } ContentBlockingNotifier::OnEvent( aChannel, false, - nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING, - aOriginNoSuffix, Nothing(), Some(event)); + nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING, origin, + Nothing(), Some(event)); } /* static */ void nsRFPService::MaybeReportFontFingerprinter( - nsIChannel* aChannel, const nsACString& aOriginNoSuffix) { + nsIChannel* aChannel, const nsACString& aURI, + const nsACString& aOriginNoSuffix) { if (!aChannel) { return; } @@ -2093,29 +2434,32 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, NS_DispatchToMainThread(NS_NewRunnableFunction( "nsRFPService::MaybeReportFontFingerprinter", [channel = nsCOMPtr{aChannel}, - originNoSuffix = nsCString(aOriginNoSuffix)]() { - nsRFPService::MaybeReportFontFingerprinter(channel, originNoSuffix); + originNoSuffix = nsCString(aOriginNoSuffix), uri = nsCString(aURI)]() { + nsRFPService::MaybeReportFontFingerprinter(channel, uri, + originNoSuffix); })); return; } + nsAutoCString uri(aURI); + nsAutoCString origin(aOriginNoSuffix); + if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Info)) { nsAutoCString filename; uint32_t lineNum = 0; uint32_t columnNum = 0; MaybeCurrentCaller(filename, lineNum, columnNum); - nsAutoCString origin(aOriginNoSuffix); MOZ_LOG(gFingerprinterDetection, LogLevel::Info, - ("Detected a potential font fingerprinter on %s in script %s:%d:%d", - origin.get(), filename.get(), lineNum, columnNum)); + ("Detected a potential font fingerprinter on %s on %s in script " + "%s:%d:%d", + origin.get(), uri.get(), filename.get(), lineNum, columnNum)); } ContentBlockingNotifier::OnEvent( aChannel, false, - nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING, - aOriginNoSuffix); + nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING, origin); } /* static */ diff --git a/toolkit/components/resistfingerprinting/nsRFPService.h b/toolkit/components/resistfingerprinting/nsRFPService.h @@ -69,6 +69,8 @@ struct JSContext; class nsIChannel; +class nsICanvasRenderingContextInternal; + namespace mozilla { class WidgetKeyboardEvent; class OriginAttributes; @@ -234,15 +236,93 @@ enum CanvasFingerprinterAlias { eLastAlias = eVariant8 }; +enum CanvasExtractionAPI : uint8_t { + ToDataURL = 0, + ToBlob = 1, + GetImageData = 2, + ReadPixels = 3 +}; + +enum CanvasUsageSource : uint64_t { + Unknown = 0, + Impossible = + 1llu << 0, // This represents API calls that I don't believe are possible + MainThread_Canvas_ImageBitmap_toDataURL = 1llu << 1, + MainThread_Canvas_ImageBitmap_toBlob = 1llu << 2, + MainThread_Canvas_ImageBitmap_getImageData = 1llu << 3, + + MainThread_Canvas_Canvas2D_toDataURL = 1llu << 4, + MainThread_Canvas_Canvas2D_toBlob = 1llu << 5, + MainThread_Canvas_Canvas2D_getImageData = 1llu << 6, + + MainThread_Canvas_WebGL_toDataURL = 1llu << 7, + MainThread_Canvas_WebGL_toBlob = 1llu << 8, + MainThread_Canvas_WebGL_getImageData = 1llu << 9, + MainThread_Canvas_WebGL_readPixels = 1llu << 10, + + MainThread_Canvas_WebGPU_toDataURL = 1llu << 11, + MainThread_Canvas_WebGPU_toBlob = 1llu << 12, + MainThread_Canvas_WebGPU_getImageData = 1llu << 13, + + MainThread_OffscreenCanvas_ImageBitmap_toDataURL = 1llu << 14, + MainThread_OffscreenCanvas_ImageBitmap_toBlob = 1llu << 15, + MainThread_OffscreenCanvas_ImageBitmap_getImageData = 1llu << 16, + + MainThread_OffscreenCanvas_Canvas2D_toDataURL = 1llu << 17, + MainThread_OffscreenCanvas_Canvas2D_toBlob = 1llu << 18, + MainThread_OffscreenCanvas_Canvas2D_getImageData = 1llu << 19, + + MainThread_OffscreenCanvas_WebGL_toDataURL = 1llu << 20, + MainThread_OffscreenCanvas_WebGL_toBlob = 1llu << 21, + MainThread_OffscreenCanvas_WebGL_getImageData = 1llu << 22, + MainThread_OffscreenCanvas_WebGL_readPixels = 1llu << 23, + + MainThread_OffscreenCanvas_WebGPU_toDataURL = 1llu << 24, + MainThread_OffscreenCanvas_WebGPU_toBlob = 1llu << 25, + MainThread_OffscreenCanvas_WebGPU_getImageData = 1llu << 26, + + Worker_OffscreenCanvas_ImageBitmap_toBlob = 1llu << 27, + Worker_OffscreenCanvas_ImageBitmap_getImageData = 1llu << 28, + + Worker_OffscreenCanvas_Canvas2D_toBlob = 1llu << 29, + Worker_OffscreenCanvas_Canvas2D_getImageData = 1llu << 30, + + Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob = 1llu << 31, + Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData = 1llu << 32, + + Worker_OffscreenCanvas_WebGL_toBlob = 1llu << 33, + Worker_OffscreenCanvas_WebGL_getImageData = 1llu << 34, + Worker_OffscreenCanvas_WebGL_readPixels = 1llu << 35, + + Worker_OffscreenCanvas_WebGPU_toBlob = 1llu << 36, + Worker_OffscreenCanvas_WebGPU_getImageData = 1llu << 37, + +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CanvasUsageSource); +nsCString CanvasUsageSourceToString(CanvasUsageSource aSource); + class CanvasUsage { public: CSSIntSize mSize; dom::CanvasContextType mType; + CanvasUsageSource mUsageSource; CanvasFeatureUsage mFeatureUsage; CanvasUsage(CSSIntSize aSize, dom::CanvasContextType aType, - CanvasFeatureUsage aFeatureUsage) - : mSize(aSize), mType(aType), mFeatureUsage(aFeatureUsage) {} + CanvasUsageSource aUsageSource, CanvasFeatureUsage aFeatureUsage) + : mSize(aSize), + mType(aType), + mUsageSource(aUsageSource), + mFeatureUsage(aFeatureUsage) {} + + static CanvasUsage CreateUsage( + bool aIsOffscreen, dom::CanvasContextType aContextType, + CanvasExtractionAPI aApi, CSSIntSize aSize, + const nsICanvasRenderingContextInternal* aContext); + + static inline CanvasUsageSource GetCanvasUsageSource( + bool isOffscreen, dom::CanvasContextType contextType, + CanvasExtractionAPI api); }; struct CanvasFingerprintingEvent { // The identity or alias of the entity doing the fingerprinting. @@ -251,21 +331,24 @@ struct CanvasFingerprintingEvent { // non-zero indicates some known fingerprinting text was used, making the // event highly likely to be fingerprinting. uint32_t knownTextBitmask; - // The API source of the fingerprinting, e.g. <canvas>.toDataURL - uint8_t source; + // A bitmap of all the sources that were used to extract canvas data + uint64_t sourcesBitmask; CanvasFingerprintingEvent() : alias(CanvasFingerprinterAlias::eNoneIdentified), knownTextBitmask(0), - source(0) {} + sourcesBitmask(0) {} CanvasFingerprintingEvent(CanvasFingerprinterAlias aAlias, - uint32_t aKnownTextBitmask, uint8_t aSource) - : alias(aAlias), knownTextBitmask(aKnownTextBitmask), source(aSource) {} + uint32_t aKnownTextBitmask, + uint64_t aSourcesBitmask) + : alias(aAlias), + knownTextBitmask(aKnownTextBitmask), + sourcesBitmask(aSourcesBitmask) {} bool operator==(const CanvasFingerprintingEvent& other) const { return alias == other.alias && knownTextBitmask == other.knownTextBitmask && - source == other.source; + sourcesBitmask == other.sourcesBitmask; } }; @@ -466,9 +549,11 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static void MaybeReportCanvasFingerprinter(nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, - nsACString& aOriginNoSuffix); + const nsACString& aURI, + const nsACString& aOriginNoSuffix); static void MaybeReportFontFingerprinter(nsIChannel* aChannel, + const nsACString& aURI, const nsACString& aOriginNoSuffix); // --------------------------------------------------------------------------