commit 02ba73c64b58571f08853cf7263659adbf1f1b81 parent 01710b96045a8b1b3560f83768559e27d355a356 Author: Atila Butkovits <abutkovits@mozilla.com> Date: Tue, 9 Dec 2025 01:29:55 +0200 Revert "Bug 1873716: Add logging for Font Fingerprinting r=timhuang" for causing ThreadSanitizer failures. This reverts commit 7f3efcc6563cab0c86447b9405548ddaaee2f294. Revert "Bug 1873716: Add utilities for identifying canvas fingerprinters in an automated fashion (crawling) r=timhuang" This reverts commit 0ca707cca577868fc2e8a20384300937dad9581d. Revert "Bug 1873716: Remove the 'shouldReport' parameter r=timhuang" This reverts commit 98b91beb72b63447b9ca1d9aab3fc6862b18ddd0. Revert "Bug 1873716: Update content blocking tests r=timhuang" This reverts commit 65643858cfd85b9d8638420165dc68279118c71f. Revert "Bug 1873716: Handle about:blank iframes that do fingerprinting r=timhuang" This reverts commit f876fc136a22d1ec693fd4207fc4ec02f56925ae. Revert "Bug 1873716: Update the heuristics used to identify different fingerprinters r=timhuang" This reverts commit 76464d08572d04c7f1ad0516008b2efb9c7f5a69. Revert "Bug 1873716: Adding more recording sites r=timhuang" This reverts commit c83fe48d8f341c62e0f20eebf1e5909d7ce78157. Revert "Bug 1873716: Finally change how we report telemetry r=timhuang" This reverts commit 39accc7ebd036a744d2bf375aeda31b841685ed3. Revert "Bug 1873716: Record (and separate activity) by source r=timhuang" This reverts commit a59040a6b05d918cd8ed64f7fd7325a7115a6f44. Revert "Bug 1873716: Switch to using a bitmask for known text r=timhuang" This reverts commit a66fab0931d975204ebe2cf943bf6885c756acf1. Revert "Bug 1873716: Move the identifier of a Fingerprinter from AntiTracking to RFP r=timhuang" This reverts commit 8c90d9ae564a073cbdb3be184c60bc166c20c0c4. Revert "Bug 1873716: Change the plumbing to use a CanvasFingerprintingEvent r=timhuang" This reverts commit 34a426c6e35d8ea90c79fe0278dc0ae508544762. Revert "Bug 1873716: Create two new canvas fingerprinting metrics for the improved telemetry design r=timhuang" This reverts commit b0fc6031702ea1db89f523b26961f051dfd78919. Revert "Bug 1873716: Simplify the fingerprinting content blocking log logic r=timhuang" This reverts commit 6f17ec54eb6e327458941936168790b31ef80fa8. Diffstat:
38 files changed, 517 insertions(+), 1497 deletions(-)
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js @@ -156,6 +156,54 @@ add_task(async function testFPPDisabled() { ); }); +// Verify that fingerprinting category is not shown if no fingerprinting +// activity is detected. +add_task(async function testFPPEnabledWithoutFingerprintingActivity() { + await SpecialPowers.pushPrefEnv({ + set: [[FINGERPRINT_PROTECTION_PREF, true]], + }); + + // Test the case where the page doesn't load any fingerprinter. + await openTestPage([], false, testCategoryNotShown); + + // Test the case where the page loads only one fingerprinter. We don't treat + // this case as suspicious fingerprinting. + await openTestPage([TEST_3RD_FONT_FP_PAGE], false, testCategoryNotShown); + + // Test the case where the page loads the same fingerprinter multiple times. + // We don't treat this case as suspicious fingerprinting. + await openTestPage( + [TEST_3RD_FONT_FP_PAGE, TEST_3RD_FONT_FP_PAGE], + false, + testCategoryNotShown + ); +}); + +// Verify that fingerprinting category is not shown if no fingerprinting +// activity is detected. +add_task( + async function testFPPEnabledWithoutSuspiciousFingerprintingActivity() { + await SpecialPowers.pushPrefEnv({ + set: [[FINGERPRINT_PROTECTION_PREF, true]], + }); + + // Test the case where the page doesn't load any fingerprinter. + await openTestPage([], false, testCategoryNotShown); + + // Test the case where the page loads only one fingerprinter. We don't treat + // this case as suspicious fingerprinting. + await openTestPage([TEST_3RD_FONT_FP_PAGE], false, testCategoryNotShown); + + // Test the case where the page loads the same fingerprinter multiple times. + // We don't treat this case as suspicious fingerprinting. + await openTestPage( + [TEST_3RD_FONT_FP_PAGE, TEST_3RD_FONT_FP_PAGE], + false, + testCategoryNotShown + ); + } +); + // Verify that fingerprinting category is properly shown and the fingerprinting // subview displays the origin of the suspicious fingerprinter. add_task(async function testFingerprintingSubview() { @@ -308,7 +356,7 @@ add_task(async function testDynamicallyLoadFingerprinter() { set: [[FINGERPRINT_PROTECTION_PREF, true]], }); - await openTestPage([], false, async (win, browser) => { + await openTestPage([TEST_3RD_FONT_FP_PAGE], false, async (win, browser) => { await openProtectionsPanel(false, win); let categoryItem = win.document.getElementById( diff --git a/docshell/shistory/ChildSHistory.cpp b/docshell/shistory/ChildSHistory.cpp @@ -9,7 +9,6 @@ #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentFrameMessageManager.h" -#include "mozilla/StaticPrefs_browser.h" #include "nsIXULRuntime.h" #include "nsComponentManagerUtils.h" #include "nsSHEntry.h" diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -477,7 +477,6 @@ mozilla::LazyLogModule gPageCacheLog("PageCache"); mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache"); mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer"); mozilla::LazyLogModule gUseCountersLog("UseCounters"); -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); namespace mozilla { @@ -17592,134 +17591,35 @@ bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const { void Document::RecordCanvasUsage(CanvasUsage& aUsage) { // Limit the number of recent canvas extraction uses that are tracked. - // Because we are now covering more canvas extraction sources, we need to - // increase this a bit - const size_t kTrackedCanvasLimit = 15; + const size_t kTrackedCanvasLimit = 8; // Timeout between different canvas extractions. const uint64_t kTimeoutUsec = 3000 * 1000; uint64_t now = PR_Now(); - - nsCString originNoSuffix; - nsCString uri; - if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { - 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())); + if ((mCanvasUsage.Length() > kTrackedCanvasLimit) || + ((now - mLastCanvasUsage) > kTimeoutUsec)) { + mCanvasUsage.ClearAndRetainStorage(); } - // 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))); - } + mCanvasUsage.AppendElement(aUsage); + mLastCanvasUsage = now; - // Update the timestamp and append the new usage. - mCanvasUsageLastTimestamp = now; - mCanvasUsageData.AppendElement(aUsage); - - nsIChannel* channel = GetChannel(); - if (!channel) { - MOZ_LOG( - gFingerprinterDetection, LogLevel::Warning, - ("Document:: %p %s no channel available", this, originNoSuffix.get())); - - // Borrowed from ReComputeResistFingerprinting - // which tells me this is probably a common problem... - auto shouldInheritFrom = [this](Document* aDoc) { - return aDoc && this->NodePrincipal() && - (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) || - this->NodePrincipal()->GetIsNullPrincipal()); - }; - - // Climb parent documents until we find a channel. - Document* docToCheck = this; - while (docToCheck && !channel) { - if (docToCheck->mParentDocument && - shouldInheritFrom(docToCheck->mParentDocument)) { - channel = docToCheck->mParentDocument->GetChannel(); - } - docToCheck = docToCheck->mParentDocument; - } - - docToCheck = this; - while (docToCheck && !channel) { - RefPtr<BrowsingContext> opener = - docToCheck->GetBrowsingContext() - ? docToCheck->GetBrowsingContext()->GetOpener() - : nullptr; - docToCheck = opener ? opener->GetDocument() : nullptr; - - if (docToCheck && shouldInheritFrom(docToCheck)) { - channel = docToCheck->GetChannel(); - } - } - - if (!channel) { - MOZ_LOG(gFingerprinterDetection, LogLevel::Warning, - ("Document:: %p %s still could not find a channel", this, - originNoSuffix.get())); - } + nsCString originNoSuffix; + if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { + return; } - nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsageData, channel, uri, + nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(), 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(), uri, originNoSuffix); + nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix); } bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; } diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -5744,10 +5744,8 @@ 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. - // 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; + nsTArray<CanvasUsage> mCanvasUsage; + uint64_t mLastCanvasUsage = 0; RefPtr<class FragmentDirective> mFragmentDirective; UniquePtr<RadioGroupContainer> mRadioGroupContainer; diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp @@ -71,7 +71,6 @@ #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/TypedArray.h" #include "mozilla/dom/VideoFrame.h" -#include "mozilla/dom/WorkerPrivate.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/CanvasShutdownManager.h" #include "mozilla/gfx/DataSurfaceHelpers.h" @@ -136,8 +135,6 @@ using namespace mozilla::image; using namespace mozilla::ipc; using namespace mozilla::layers; -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); - namespace mozilla::dom { // Cap sigma to avoid overly large temp surfaces. @@ -2279,18 +2276,12 @@ UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer( mBufferProvider->ReturnSnapshot(snapshot.forget()); - if (ret) { - nsRFPService::PotentiallyDumpImage( - PrincipalOrNull(), ret.get(), out_imageSize->width, - out_imageSize->height, - out_imageSize->width * out_imageSize->height * 4); - if (aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { - nsRFPService::RandomizePixels( - GetCookieJarSettings(), PrincipalOrNull(), ret.get(), - out_imageSize->width, out_imageSize->height, - out_imageSize->width * out_imageSize->height * 4, - SurfaceFormat::A8R8G8B8_UINT32); - } + if (ret && aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { + nsRFPService::RandomizePixels( + GetCookieJarSettings(), PrincipalOrNull(), ret.get(), + out_imageSize->width, out_imageSize->height, + out_imageSize->width * out_imageSize->height * 4, + SurfaceFormat::A8R8G8B8_UINT32); } return ret; @@ -4575,65 +4566,25 @@ void CanvasRenderingContext2D::FillText(const nsAString& aText, double aX, const Optional<double>& aMaxWidth, ErrorResult& aError) { // We try to match the most commonly observed strings used by canvas - // fingerprinting scripts. - MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, - ("mFillTextCalls %i FillText: " - "\"%s\"\n", - mFillTextCalls, NS_ConvertUTF16toUTF8(aText).get())); + // fingerprinting scripts. We do a prefix match, because that means having to + // match fewer bytes and sometimes the strings is followed by a few random + // characters. + // - Cwm fjordbank gly + // Used by FingerprintJS + // (https://github.com/fingerprintjs/fingerprintjs/blob/4c4b2c8455e701b8341b2b766d1939cf5de4b615/src/sources/canvas.ts#L119) + // and others + // - Hel$&?6%){mZ+#@ + // - <@nv45. F1n63r,Pr1n71n6! + // Usually there are at most a handful (usually ~1/2) fillText calls by + // fingerprinters if (mFillTextCalls <= 5) { - if (aText == u"Cwm fjordbank glyphs vext quiz, 😃"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_1; - } else if (aText == u"Cwm fjordbank gly 😃"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_1; - } else if (StringBeginsWith(aText, u"Hel$&?6%"_ns)) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_2; - } else if (StringBeginsWith(aText, u"<@nv45. "_ns)) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_3; - } else if (aText == u"Cañvas FP 😎 12345"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_4; - } else if (StringBeginsWith(aText, u"❤️🤪🎉👋"_ns)) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_5; - } else if (aText == u"SomeCanvasFingerPrint.65@345876"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_6; - } else if (aText == u"Browser,Signal <canvas> 2.0"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_7; - } else if (aText == u"@Browsers~%fingGPRint$&,<canvas>"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_8; - } else if (aText == u"M"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_9; - } else if (aText == u"E"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_10; - } else if (aText == u"g"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_11; - } else if (aText == u"Soft Ruddy Foothold 2"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_12; - } else if (aText == u"!H71JCaj)]# 1@#"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_13; - } else if (aText == u"oubrg5h56e@!$3t4"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_14; - } else if (aText == u"Cwm fjordbank glyphs vext quiz,"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_15; - } else if (aText == u"ClientJS,org <canvas> 1.0"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_16; - } else if (aText == u"IaID,org <canvas> 1.0"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_17; - } else if (aText == u"conviva"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_18; - } else if (aText == u"Random Text WMwmil10Oo"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_19; - } else if (aText == u"-0.5753861119575491"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_20; - } else if (aText == u"0.8178819121159085"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_21; - } else if (StringBeginsWith(aText, u"Cwm fjordbank"_ns)) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_22; - } else if (StringBeginsWith(aText, u"iO0A"_ns)) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_23; - } else if (aText == u"<@nv45. F1n63r,Pr1n71n6!"_ns) { - mFeatureUsage |= CanvasFeatureUsage::KnownText_24; - } - } - mFillTextCalls++; + if (StringBeginsWith(aText, u"Cwm fjord"_ns) || + StringBeginsWith(aText, u"Hel$&?6%"_ns) || + StringBeginsWith(aText, u"<@nv45. "_ns)) { + mFeatureUsage |= CanvasFeatureUsage::KnownFingerprintText; + } + mFillTextCalls++; + } DebugOnly<UniquePtr<TextMetrics>> metrics = DrawOrMeasureText( aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, aError); @@ -6518,8 +6469,6 @@ already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData( h = 1; } - RecordCanvasUsage(CanvasExtractionAPI::GetImageData, CSSIntSize(w, h)); - JS::Rooted<JSObject*> array(aCx); aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal, array.address()); @@ -6618,10 +6567,6 @@ nsresult CanvasRenderingContext2D::GetImageDataArray( do { uint8_t* randomData; - const IntSize size = readback->GetSize(); - nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), rawData.mData, - size.width, size.height, - size.height * size.width * 4); if (extractionBehavior == CanvasUtils::ImageExtraction::Placeholder) { // Since we cannot call any GC-able functions (like requesting the RNG // service) after we call JS_GetUint8ClampedArrayData, we will @@ -6632,6 +6577,7 @@ nsresult CanvasRenderingContext2D::GetImageDataArray( // need to calculate random noises if we are going to use the place // holder. + const IntSize size = readback->GetSize(); nsRFPService::RandomizePixels(GetCookieJarSettings(), PrincipalOrNull(), rawData.mData, size.width, size.height, size.height * size.width * 4, diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp @@ -1358,7 +1358,6 @@ UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer( const auto& premultAlpha = notLost->info.options.premultipliedAlpha; *out_imageSize = dataSurface->GetSize(); - nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); if (aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { return gfxUtils::GetImageBufferWithRandomNoise( dataSurface, premultAlpha, GetCookieJarSettings(), PrincipalOrNull(), @@ -1386,7 +1385,6 @@ ClientWebGLContext::GetInputStream( RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface(); const auto& premultAlpha = notLost->info.options.premultipliedAlpha; - nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { return gfxUtils::GetInputStreamWithRandomNoise( dataSurface, premultAlpha, mimeType, encoderOptions, @@ -5356,30 +5354,26 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, if (extraction == CanvasUtils::ImageExtraction::Placeholder) { dom::GeneratePlaceholderCanvasData(range->size(), range->Elements()); - } else { - RecordCanvasUsage(CanvasExtractionAPI::ReadPixels, - CSSIntSize(width, height)); - if (extraction == CanvasUtils::ImageExtraction::Randomize) { - const auto pii = webgl::PackingInfoInfo::For(desc.pi); - // DoReadPixels() requres pii to be Some(). - MOZ_ASSERT(pii.isSome()); - - // With WebGL, the alpha channel is always the last element (if it - // exists) in the pixel. With nsRFPService::RandomizeElements, we do - // random % (pii->elementsPerPixel - 1) + offset to get the channel - // we want to randomize. With the offset being 0, we avoid the last - // element, which is the alpha channel. - // If WebGL had ARGB or some other format where the alpha channel - // was not the last element, we would need to adjust the offset. - constexpr uint8_t alphaChannelOffset = 0; - bool hasAlphaChannel = - format == LOCAL_GL_SRGB_ALPHA || format == LOCAL_GL_RGBA || - format == LOCAL_GL_BGRA || format == LOCAL_GL_LUMINANCE_ALPHA; - nsRFPService::RandomizeElements( - GetCookieJarSettings(), PrincipalOrNull(), range->data(), - range->size_bytes(), pii->elementsPerPixel, pii->bytesPerElement, - alphaChannelOffset, hasAlphaChannel); - } + } else if (extraction == CanvasUtils::ImageExtraction::Randomize) { + const auto pii = webgl::PackingInfoInfo::For(desc.pi); + // DoReadPixels() requres pii to be Some(). + MOZ_ASSERT(pii.isSome()); + + // With WebGL, the alpha channel is always the last element (if it + // exists) in the pixel. With nsRFPService::RandomizeElements, we do + // random % (pii->elementsPerPixel - 1) + offset to get the channel + // we want to randomize. With the offset being 0, we avoid the last + // element, which is the alpha channel. + // If WebGL had ARGB or some other format where the alpha channel + // was not the last element, we would need to adjust the offset. + constexpr uint8_t alphaChannelOffset = 0; + bool hasAlphaChannel = + format == LOCAL_GL_SRGB_ALPHA || format == LOCAL_GL_RGBA || + format == LOCAL_GL_BGRA || format == LOCAL_GL_LUMINANCE_ALPHA; + nsRFPService::RandomizeElements( + GetCookieJarSettings(), PrincipalOrNull(), range->data(), + range->size_bytes(), pii->elementsPerPixel, pii->bytesPerElement, + alphaChannelOffset, hasAlphaChannel); } } }); diff --git a/dom/canvas/ImageBitmapRenderingContext.cpp b/dom/canvas/ImageBitmapRenderingContext.cpp @@ -195,18 +195,12 @@ mozilla::UniquePtr<uint8_t[]> ImageBitmapRenderingContext::GetImageBuffer( UniquePtr<uint8_t[]> ret = gfx::SurfaceToPackedBGRA(data); - if (ret) { - nsRFPService::PotentiallyDumpImage( - PrincipalOrNull(), ret.get(), data->GetSize().width, - data->GetSize().height, - data->GetSize().width * data->GetSize().height * 4); - if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { - nsRFPService::RandomizePixels( - GetCookieJarSettings(), PrincipalOrNull(), ret.get(), - data->GetSize().width, data->GetSize().height, - data->GetSize().width * data->GetSize().height * 4, - gfx::SurfaceFormat::A8R8G8B8_UINT32); - } + if (ret && ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { + nsRFPService::RandomizePixels( + GetCookieJarSettings(), PrincipalOrNull(), ret.get(), + data->GetSize().width, data->GetSize().height, + data->GetSize().width * data->GetSize().height * 4, + gfx::SurfaceFormat::A8R8G8B8_UINT32); } return ret; } diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp @@ -16,7 +16,6 @@ #include "WebGLChild.h" #include "mozilla/Atomics.h" #include "mozilla/CheckedInt.h" -#include "mozilla/Logging.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/OffscreenCanvasBinding.h" @@ -34,8 +33,6 @@ namespace mozilla::dom { -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); - OffscreenCanvasCloneData::OffscreenCanvasCloneData( OffscreenCanvasDisplayHelper* aDisplay, uint32_t aWidth, uint32_t aHeight, layers::LayersBackend aCompositorBackend, bool aNeutered, bool aIsWriteOnly, @@ -518,14 +515,9 @@ already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob( this, nsContentUtils::GetCurrentJSContext(), mCurrentContext ? mCurrentContext->PrincipalOrNull() : nullptr); - if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { - GetContext()->RecordCanvasUsage(CanvasExtractionAPI::ToBlob, - GetWidthHeight()); - } CanvasRenderingContextHelper::ToBlob(callback, type, encodeOptions, /* aUsingCustomOptions */ false, extractionBehaviour, aRv); - if (aRv.Failed()) { promise->MaybeReject(std::move(aRv)); } @@ -567,12 +559,6 @@ already_AddRefed<Promise> OffscreenCanvas::ToBlob(JSContext* aCx, CanvasUtils::ImageExtractionResult( this, aCx, mCurrentContext ? mCurrentContext->PrincipalOrNull() : nullptr); - - if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { - GetContext()->RecordCanvasUsage(CanvasExtractionAPI::ToBlob, - GetWidthHeight()); - } - CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams, extractionBehaviour, aRv); @@ -641,7 +627,6 @@ FontVisibility OffscreenCanvas::GetFontVisibility() const { } void OffscreenCanvas::ReportBlockedFontFamily(const nsCString& aMsg) const { - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Info, ("%s", aMsg.get())); if (Maybe<uint64_t> windowID = GetWindowID()) { nsContentUtils::ReportToConsoleByWindowID(NS_ConvertUTF8toUTF16(aMsg), nsIScriptError::warningFlag, diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h @@ -175,9 +175,6 @@ class OffscreenCanvas final : public DOMEventTargetHelper, private: ~OffscreenCanvas(); - void RecordCanvasUsage(CanvasExtractionAPI aExtractionAPI, - CanvasUtils::ImageExtraction aExtractionBehaviour); - already_AddRefed<EncodeCompleteCallback> CreateEncodeCompleteCallback( Promise* aPromise); diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp @@ -591,24 +591,21 @@ UniquePtr<uint8_t[]> OffscreenCanvasDisplayHelper::GetImageBuffer( return nullptr; } - nsIPrincipal* principal = nullptr; - nsICookieJarSettings* cookieJarSettings = nullptr; - { - // This function is never called with mOffscreenCanvas set, so we skip - // the check for it. - MutexAutoLock lock(mMutex); - MOZ_ASSERT(!mOffscreenCanvas); - - if (mCanvasElement) { - principal = mCanvasElement->NodePrincipal(); - cookieJarSettings = mCanvasElement->OwnerDoc()->CookieJarSettings(); - } - } - nsRFPService::PotentiallyDumpImage( - principal, imageBuffer.get(), dataSurface->GetSize().width, - dataSurface->GetSize().height, - dataSurface->GetSize().width * dataSurface->GetSize().height * 4); if (aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { + nsIPrincipal* principal = nullptr; + nsICookieJarSettings* cookieJarSettings = nullptr; + { + // This function is never called with mOffscreenCanvas set, so we skip + // the check for it. + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mOffscreenCanvas); + + if (mCanvasElement) { + principal = mCanvasElement->NodePrincipal(); + cookieJarSettings = mCanvasElement->OwnerDoc()->CookieJarSettings(); + } + } + nsRFPService::RandomizePixels( cookieJarSettings, principal, imageBuffer.get(), dataSurface->GetSize().width, dataSurface->GetSize().height, diff --git a/dom/canvas/nsICanvasRenderingContextInternal.cpp b/dom/canvas/nsICanvasRenderingContextInternal.cpp @@ -12,15 +12,10 @@ #include "mozilla/dom/Event.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" -#include "mozilla/dom/WorkerRunnable.h" #include "mozilla/gfx/DrawTargetRecording.h" #include "nsContentUtils.h" #include "nsPIDOMWindow.h" -#include "nsRFPService.h" #include "nsRefreshDriver.h" -#include "nsThreadUtils.h" - -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); nsICanvasRenderingContextInternal::nsICanvasRenderingContextInternal() = default; @@ -45,87 +40,6 @@ nsIGlobalObject* nsICanvasRenderingContextInternal::GetParentObject() const { return nullptr; } -class RecordCanvasUsageRunnable final - : public mozilla::dom::WorkerMainThreadRunnable { - public: - RecordCanvasUsageRunnable(mozilla::dom::WorkerPrivate* aWorkerPrivate, - const mozilla::CanvasUsage& aUsage) - : WorkerMainThreadRunnable(aWorkerPrivate, - "RecordCanvasUsageRunnable"_ns), - mUsage(aUsage) { - MOZ_ASSERT(aWorkerPrivate); - aWorkerPrivate->AssertIsOnWorkerThread(); - } - - protected: - MOZ_CAN_RUN_SCRIPT_BOUNDARY bool MainThreadRun() override { - mozilla::AssertIsOnMainThread(); - RefPtr<mozilla::dom::Document> doc; - if (!mWorkerRef) { - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error, - ("RecordCanvasUsageRunnable::MainThreadRun - null mWorkerRef")); - return false; - } - auto* priv = mWorkerRef->Private(); - if (!priv) { - MOZ_LOG( - gFingerprinterDetection, mozilla::LogLevel::Error, - ("RecordCanvasUsageRunnable::MainThreadRun - null worker private")); - return false; - } - doc = priv->GetDocument(); - if (!doc) { - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error, - ("RecordCanvasUsageRunnable::MainThreadRun - null document")); - return false; - } - doc->RecordCanvasUsage(mUsage); - return true; - } - - private: - mozilla::CanvasUsage mUsage; -}; - -void nsICanvasRenderingContextInternal::RecordCanvasUsage( - mozilla::CanvasExtractionAPI aAPI, mozilla::CSSIntSize size) const { - mozilla::dom::CanvasContextType contextType; - if (mCanvasElement) { - contextType = mCanvasElement->GetCurrentContextType(); - auto usage = - mozilla::CanvasUsage::CreateUsage(false, contextType, aAPI, size, this); - mCanvasElement->OwnerDoc()->RecordCanvasUsage(usage); - } - if (mOffscreenCanvas) { - contextType = mOffscreenCanvas->GetContextType(); - auto usage = - mozilla::CanvasUsage::CreateUsage(true, contextType, aAPI, size, this); - if (NS_IsMainThread()) { - if (nsPIDOMWindowInner* inner = - mOffscreenCanvas->GetOwnerGlobal()->GetAsInnerWindow()) { - if (mozilla::dom::Document* doc = inner->GetExtantDoc()) { - doc->RecordCanvasUsage(usage); - } - } - } else { - mozilla::dom::WorkerPrivate* workerPrivate = - mozilla::dom::GetCurrentThreadWorkerPrivate(); - if (workerPrivate) { - RefPtr<RecordCanvasUsageRunnable> runnable = - new RecordCanvasUsageRunnable(workerPrivate, usage); - mozilla::ErrorResult rv; - runnable->Dispatch(workerPrivate, mozilla::dom::WorkerStatus::Canceling, - rv); - if (rv.Failed()) { - rv.SuppressException(); - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error, - ("RecordCanvasUsageRunnable dispatch failed")); - } - } - } - } -} - nsIPrincipal* nsICanvasRenderingContextInternal::PrincipalOrNull() const { if (mCanvasElement) { return mCanvasElement->NodePrincipal(); diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -21,6 +21,7 @@ #include "nsIDocShell.h" #include "nsIInputStream.h" #include "nsISupports.h" +#include "nsRFPService.h" #include "nsRefreshObservers.h" #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \ @@ -231,9 +232,6 @@ class nsICanvasRenderingContextInternal : public nsISupports, bool DispatchEvent(const nsAString& eventName, mozilla::CanBubble aCanBubble, mozilla::Cancelable aIsCancelable) const; - void RecordCanvasUsage(mozilla::CanvasExtractionAPI aAPI, - mozilla::CSSIntSize size) const; - protected: RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement; RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas; diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp @@ -928,9 +928,16 @@ nsresult HTMLCanvasElement::ExtractData(JSContext* aCx, if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { auto size = GetWidthHeight(); - auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(), - CanvasExtractionAPI::ToDataURL, size, - GetCurrentContext()); + 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); OwnerDoc()->RecordCanvasUsage(usage); } @@ -1090,13 +1097,6 @@ 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/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp @@ -4199,7 +4199,9 @@ void BrowserChild::NotifyContentBlockingEvent( const Maybe< mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { if (!IPCOpen()) { return; } @@ -4208,7 +4210,8 @@ void BrowserChild::NotifyContentBlockingEvent( if (NS_SUCCEEDED(PrepareRequestData(aChannel, requestData))) { (void)SendNotifyContentBlockingEvent( aEvent, requestData, aBlocked, PromiseFlatCString(aTrackingOrigin), - aTrackingFullHashes, aReason, aCanvasFingerprintingEvent); + aTrackingFullHashes, aReason, aCanvasFingerprinter, + aCanvasFingerprinterKnownText); } } diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h @@ -702,7 +702,9 @@ class BrowserChild final : public nsMessageManagerScriptExecutor, const Maybe< ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent); + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText); already_AddRefed<nsIDragSession> GetDragSession(); void SetDragSession(nsIDragSession* aSession); diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp @@ -3083,7 +3083,9 @@ mozilla::ipc::IPCResult BrowserParent::RecvNotifyContentBlockingEvent( const Maybe< mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<mozilla::ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool>& aCanvasFingerprinterKnownText) { RefPtr<BrowsingContext> bc = GetBrowsingContext(); if (!bc || bc->IsDiscarded()) { @@ -3107,9 +3109,9 @@ mozilla::ipc::IPCResult BrowserParent::RecvNotifyContentBlockingEvent( aRequestData.matchedList()); request->SetCanceledReason(aRequestData.canceledReason()); - wgp->NotifyContentBlockingEvent(aEvent, request, aBlocked, aTrackingOrigin, - aTrackingFullHashes, aReason, - aCanvasFingerprintingEvent); + wgp->NotifyContentBlockingEvent( + aEvent, request, aBlocked, aTrackingOrigin, aTrackingFullHashes, aReason, + aCanvasFingerprinter, aCanvasFingerprinterKnownText); return IPC_OK(); } diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h @@ -306,7 +306,9 @@ class BrowserParent final : public PBrowserParent, nsTArray<nsCString>&& aTrackingFullHashes, const Maybe<mozilla::ContentBlockingNotifier:: StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent); + const Maybe<mozilla::ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool>& aCanvasFingerprinterKnownText); mozilla::ipc::IPCResult RecvNavigationFinished(); diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl @@ -30,7 +30,6 @@ include PTabContext; include PBackgroundSharedTypes; include "mozilla/AntiTrackingIPCUtils.h"; -include "mozilla/nsRFPIPCUtils.h"; include "mozilla/dom/BindingIPCUtils.h"; include "mozilla/dom/CSPMessageUtils.h"; include "mozilla/dom/PolicyContainerMessageUtils.h"; @@ -104,7 +103,7 @@ using mozilla::ScrollFlags from "mozilla/PresShellForwards.h"; using struct InputFormData from "mozilla/dom/SessionStoreMessageUtils.h"; using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h"; using mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason from "mozilla/ContentBlockingNotifier.h"; -using mozilla::CanvasFingerprintingEvent from "nsRFPService.h"; +using mozilla::ContentBlockingNotifier::CanvasFingerprinter from "mozilla/ContentBlockingNotifier.h"; using mozilla::dom::CallerType from "mozilla/dom/BindingDeclarations.h"; using mozilla::dom::EmbedderElementEventType from "mozilla/dom/TabMessageTypes.h"; using mozilla::IntrinsicSize from "nsIFrame.h"; @@ -547,7 +546,8 @@ parent: bool aBlocked, nsCString aTrackingOrigin, nsCString[] aTrackingFullHashes, StorageAccessPermissionGrantedReason? aReason, - CanvasFingerprintingEvent? aCanvasFingerprintingEvent); + CanvasFingerprinter? aCanvasFingerprinter, + bool? aCanvasFingerprinterKnownText); async NavigationFinished(); diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp @@ -579,7 +579,9 @@ void WindowGlobalParent::NotifyContentBlockingEvent( const nsTArray<nsCString>& aTrackingFullHashes, const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { MOZ_ASSERT(NS_IsMainThread()); DebugOnly<bool> isCookiesBlocked = aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || @@ -598,7 +600,7 @@ void WindowGlobalParent::NotifyContentBlockingEvent( Maybe<uint32_t> event = GetContentBlockingLog()->RecordLogParent( aTrackingOrigin, aEvent, aBlocked, aReason, aTrackingFullHashes, - aCanvasFingerprintingEvent); + aCanvasFingerprinter, aCanvasFingerprinterKnownText); // Notify the OnContentBlockingEvent if necessary. if (event) { @@ -1656,8 +1658,12 @@ void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { GetContentBlockingLog()->ReportLog(); if (mDocumentURI && net::SchemeIsHttpOrHttps(mDocumentURI)) { + bool incrementedTopLevelContentDocumentsDestroyed = + pageUseCounterResult.contains( + PageUseCounterResultBits::DATA_RECEIVED); GetContentBlockingLog()->ReportCanvasFingerprintingLog( - DocumentPrincipal()); + DocumentPrincipal(), + incrementedTopLevelContentDocumentsDestroyed); GetContentBlockingLog()->ReportFontFingerprintingLog( DocumentPrincipal()); GetContentBlockingLog()->ReportEmailTrackingLog(DocumentPrincipal()); diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h @@ -196,7 +196,9 @@ class WindowGlobalParent final : public WindowContext, const Maybe< ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent); + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText); ContentBlockingLog* GetContentBlockingLog() { return &mContentBlockingLog; } diff --git a/dom/webgpu/CanvasContext.cpp b/dom/webgpu/CanvasContext.cpp @@ -337,7 +337,6 @@ mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer( RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface(); *out_imageSize = dataSurface->GetSize(); - nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { gfxUtils::GetImageBufferWithRandomNoise(dataSurface, /* aIsAlphaPremultiplied */ true, @@ -361,7 +360,6 @@ NS_IMETHODIMP CanvasContext::GetInputStream( RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface(); - nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { return gfxUtils::GetInputStreamWithRandomNoise( dataSurface, /* aIsAlphaPremultiplied */ true, aMimeType, diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp @@ -137,7 +137,6 @@ static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate"); static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts"); -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; } @@ -6774,7 +6773,6 @@ FontVisibility WorkerPrivate::GetFontVisibility() const { } void WorkerPrivate::ReportBlockedFontFamily(const nsCString& aMsg) const { - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Info, ("%s", aMsg.get())); nsContentUtils::ReportToConsoleNonLocalized(NS_ConvertUTF8toUTF16(aMsg), nsIScriptError::warningFlag, "Security"_ns, GetDocument()); diff --git a/gfx/src/nsFontCache.cpp b/gfx/src/nsFontCache.cpp @@ -17,8 +17,6 @@ using mozilla::services::GetObserverService; NS_IMPL_ISUPPORTS(nsFontCache, nsIObserver) -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); - // The Init and Destroy methods are necessary because it's not // safe to call AddObserver from a constructor or RemoveObserver // from a destructor. That should be fixed. @@ -119,14 +117,7 @@ void nsFontCache::DetectFontFingerprinting(const nsFont& aFont) { // number of cache misses in a short amount of time, which indicates the // usage of an unreasonable amount of different fonts by the web page. - if (aFont.family.families.list.IsEmpty()) { - return; - } - - if (!MOZ_LOG_TEST(gFingerprinterDetection, mozilla::LogLevel::Info) && - mReportedProbableFingerprinting) { - // We've already reported fingerprinting for this document, and logging - // isn't enabled, so skip the rest of the detection logic. + if (mReportedProbableFingerprinting || aFont.family.families.list.IsEmpty()) { return; } @@ -152,39 +143,20 @@ void nsFontCache::DetectFontFingerprinting(const nsFont& aFont) { uint16_t fontsMissedRecently = 0; bool clearMissedFonts = false; - if (!mReportedProbableFingerprinting) { - for (auto iter = missedFonts->Iter(); !iter.Done(); iter.Next()) { - if (now - kFingerprintingLastNSec <= iter.Data()) { - if (++fontsMissedRecently > kFingerprintingCacheMissThreshold) { - mContext->Document()->RecordFontFingerprinting(); - mReportedProbableFingerprinting = true; - clearMissedFonts = true; - break; - } - } else { - // Remove the old entries from missed cache list. - iter.Remove(); - } - } - if (mReportedProbableFingerprinting) { - // We detected a font fingerprinter, so print out all the fonts they - // queries and missed - for (auto iter = missedFonts->Iter(); !iter.Done(); iter.Next()) { - MOZ_LOG( - gFingerprinterDetection, mozilla::LogLevel::Info, - ("Font Fingerprinting Tripped | Document %p | Font Family | %s", - mContext->Document(), NS_ConvertUTF16toUTF8(iter.Key()).get())); + for (auto iter = missedFonts->Iter(); !iter.Done(); iter.Next()) { + if (now - kFingerprintingLastNSec <= iter.Data()) { + if (++fontsMissedRecently > kFingerprintingCacheMissThreshold) { + mContext->Document()->RecordFontFingerprinting(); + mReportedProbableFingerprinting = true; + clearMissedFonts = true; + break; } + } else { + // Remove the old entries from missed cache list. + iter.Remove(); } - } else { - // I've already reported the font fingerprinting, but I want to report each - // additional font - MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Info, - ("Font Fingerprinting Tripped | Document %p | Font Family | %s", - mContext->Document(), NS_ConvertUTF16toUTF8(key).get())); } - if (!MOZ_LOG_TEST(gFingerprinterDetection, mozilla::LogLevel::Info) && - clearMissedFonts) { + if (clearMissedFonts) { missedFonts->Clear(); } } diff --git a/gfx/src/nsFontCache.h b/gfx/src/nsFontCache.h @@ -54,11 +54,11 @@ class nsFontCache final : public nsIObserver { // Number of cache misses before we assume that a font fingerprinting attempt // is being made. - static constexpr int32_t kFingerprintingCacheMissThreshold = 10; + static constexpr int32_t kFingerprintingCacheMissThreshold = 20; // We assume that fingerprinters will lookup a large number of fonts in a // short amount of time. static constexpr PRTime kFingerprintingLastNSec = - PRTime(PR_USEC_PER_SEC) * 6; // 6 seconds + PRTime(PR_USEC_PER_SEC) * 3; // 3 seconds static_assert(kFingerprintingCacheMissThreshold < kMaxCacheEntries); diff --git a/js/xpconnect/loader/mozJSModuleLoader.cpp b/js/xpconnect/loader/mozJSModuleLoader.cpp @@ -65,7 +65,6 @@ #include "mozilla/ProfilerMarkers.h" #include "mozilla/ResultExtensions.h" #include "mozilla/ScriptPreloader.h" -#include "mozilla/StaticPrefs_browser.h" #include "mozilla/Try.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/ReferrerPolicyBinding.h" diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp @@ -113,8 +113,6 @@ using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::layers; -static LazyLogModule gFingerprinterDetection("FingerprinterDetection"); - /** * Layer UserData for ContainerLayers that want to be notified * of local invalidations of them and their descendant layers. @@ -3083,7 +3081,6 @@ bool nsPresContext::ShouldResistFingerprinting(RFPTarget aTarget) const { } void nsPresContext::ReportBlockedFontFamily(const nsCString& aMsg) const { - MOZ_LOG(gFingerprinterDetection, LogLevel::Info, ("%s", aMsg.get())); nsContentUtils::ReportToConsoleNonLocalized(NS_ConvertUTF8toUTF16(aMsg), nsIScriptError::warningFlag, "Security"_ns, Document()); diff --git a/toolkit/components/antitracking/AntiTrackingIPCUtils.h b/toolkit/components/antitracking/AntiTrackingIPCUtils.h @@ -54,6 +54,14 @@ struct ParamTraits<nsILoadInfo::StoragePermissionState> nsILoadInfo::StoragePermissionState, nsILoadInfo::StoragePermissionState::NoStoragePermission, nsILoadInfo::StoragePermissionState::InactiveStoragePermission> {}; + +// ContentBlockingNotifier::CanvasFingerprinter over IPC. +template <> +struct ParamTraits<mozilla::ContentBlockingNotifier::CanvasFingerprinter> + : public ContiguousEnumSerializerInclusive< + mozilla::ContentBlockingNotifier::CanvasFingerprinter, + mozilla::ContentBlockingNotifier::CanvasFingerprinter::eFingerprintJS, + mozilla::ContentBlockingNotifier::CanvasFingerprinter::eMaybe> {}; } // namespace IPC #endif // mozilla_antitrackingipcutils_h diff --git a/toolkit/components/antitracking/ContentBlockingLog.cpp b/toolkit/components/antitracking/ContentBlockingLog.cpp @@ -25,8 +25,6 @@ #include "mozilla/StaticPtr.h" #include "mozilla/glean/AntitrackingMetrics.h" -static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); - namespace mozilla { namespace { @@ -50,7 +48,9 @@ Maybe<uint32_t> ContentBlockingLog::RecordLogParent( const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, const nsTArray<nsCString>& aTrackingFullHashes, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { MOZ_ASSERT(XRE_IsParentProcess()); uint32_t events = GetContentBlockingEventsInLog(); @@ -135,7 +135,8 @@ Maybe<uint32_t> ContentBlockingLog::RecordLogParent( "We don't expected to see blocked " "STATE_ALLOWED_CANVAS_FINGERPRINTING"); entry = RecordLogInternal(aOrigin, aType, blockedValue, Nothing(), {}, - aCanvasFingerprintingEvent); + aCanvasFingerprinter, + aCanvasFingerprinterKnownText); // Replace the flag using the suspicious fingerprinting event so that we // can report the event if we detect suspicious fingerprinting. @@ -204,7 +205,7 @@ void ContentBlockingLog::ReportLog() { } void ContentBlockingLog::ReportCanvasFingerprintingLog( - nsIPrincipal* aFirstPartyPrincipal) { + nsIPrincipal* aFirstPartyPrincipal, bool aShouldReport) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aFirstPartyPrincipal); @@ -215,6 +216,8 @@ void ContentBlockingLog::ReportCanvasFingerprintingLog( } bool hasCanvasFingerprinter = false; + bool canvasFingerprinterKnownText = false; + Maybe<ContentBlockingNotifier::CanvasFingerprinter> canvasFingerprinter; for (const auto& originEntry : mLog) { if (!originEntry.mData) { continue; @@ -224,118 +227,55 @@ void ContentBlockingLog::ReportCanvasFingerprintingLog( if (logEntry.mType != nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING) { continue; - } else if (logEntry.mCanvasFingerprintingEvent.isSome() == false) { - // Little confused about how we could get here, but I did see a crash - { - nsAutoCString firstPartyOrigin; - aFirstPartyPrincipal->GetOriginNoSuffix(firstPartyOrigin); - MOZ_LOG(gFingerprinterDetection, LogLevel::Error, - ("ContentBlockingLog::ReportCanvasFingerprintingLog: " - "logEntry has no CanvasFingerprintingEvent " - "(firstPartyOrigin=%s)", - firstPartyOrigin.get())); - } - continue; - } - hasCanvasFingerprinter = true; - - auto canvasFingerprintingEvent = - logEntry.mCanvasFingerprintingEvent.value(); - - // ---------------------------------- - // This function iterates through all source bits, incrementing for each - // individually And then incrementing for the combination of all source - // bits set. It is used for both of the specific metrics we record. - auto IncrementBySources = - [](const CanvasFingerprintingEvent canvasFingerprintingEvent, - glean::impl::DualLabeledCounterMetric metric, - const nsCString& key) { - for (uint64_t b = canvasFingerprintingEvent.sourcesBitmask; b; - b &= (b - 1)) { - // Unlike knownText, we use the full number (e.g. 256) rather than - // the exponent (e.g. 8) - uint32_t singleSetBit_Source = b & (~b + 1); - - nsAutoCString category; - category.AppendInt(singleSetBit_Source); - - // Increment once for each known text bit and source bit - // combination - - metric.Get(key, category).Add(); - } - // And increment once for each known text bit and source bit - // combination. Make this an Info-level log because combinations - // MUST be added to the metric definition to be useful. - MOZ_LOG(gFingerprinterDetection, LogLevel::Info, - ("ContentBlockingLog::ReportCanvasFingerprintingLog: " - "Incrementing for combined sources bitmask %" PRIu64, - canvasFingerprintingEvent.sourcesBitmask)); - nsAutoCString category; - category.AppendInt(canvasFingerprintingEvent.sourcesBitmask); - metric.Get(key, category).Add(); - }; - - // ---------------------------------- - // First cover canvas_fingerprinting_type_text_by_source_per_tab2 - // We do this for each log entry - if (!canvasFingerprintingEvent.knownTextBitmask) { - nsAutoCString key; - key.AppendLiteral("none"); - - IncrementBySources( - canvasFingerprintingEvent, - glean::contentblocking:: - canvas_fingerprinting_type_text_by_source_per_tab2, - key); - } else { - // Iterate over each set bit in the bitmask - for (uint32_t b = canvasFingerprintingEvent.knownTextBitmask; b; - b &= (b - 1)) { - uint32_t singleSetBit_Text = b & (~b + 1); - uint32_t exponent = mozilla::CountTrailingZeroes32(singleSetBit_Text); - - nsAutoCString key; - key.AppendInt(exponent); - - IncrementBySources( - canvasFingerprintingEvent, - glean::contentblocking:: - canvas_fingerprinting_type_text_by_source_per_tab2, - key); - } } - // ---------------------------------- - // Second, cover canvas_fingerprinting_type_alias_by_source_per_tab2 - // We also do this for each log entry - nsAutoCString key; - key.AppendInt(static_cast<uint32_t>(canvasFingerprintingEvent.alias)); - - IncrementBySources( - canvasFingerprintingEvent, - glean::contentblocking:: - canvas_fingerprinting_type_alias_by_source_per_tab2, - key); + // Select the log entry with the highest fingerprinting likelihood, + // that primarily means preferring those with a FingerprinterKnownText. + if (!hasCanvasFingerprinter || + (!canvasFingerprinterKnownText && + *logEntry.mCanvasFingerprinterKnownText) || + (!canvasFingerprinterKnownText && canvasFingerprinter.isNothing() && + logEntry.mCanvasFingerprinter.isSome())) { + hasCanvasFingerprinter = true; + canvasFingerprinterKnownText = *logEntry.mCanvasFingerprinterKnownText; + canvasFingerprinter = logEntry.mCanvasFingerprinter; + } } } - // ---------------------------------- - // Finally, cover the overall 'was there any canvas fingerprinting' metric - // canvas_fingerprinting_per_tab2 + auto label = + glean::contentblocking::CanvasFingerprintingPerTabLabel::eUnknown; + auto labelMatched = + glean::contentblocking::CanvasFingerprintingPerTabLabel::eUnknownMatched; + if (hasCanvasFingerprinter && canvasFingerprinterKnownText) { + label = glean::contentblocking::CanvasFingerprintingPerTabLabel::eKnownText; + labelMatched = glean::contentblocking::CanvasFingerprintingPerTabLabel:: + eKnownTextMatched; + } if (!hasCanvasFingerprinter) { - // Increment the global 'did the page have any' metric - glean::contentblocking::canvas_fingerprinting_per_tab2 - .EnumGet( - glean::contentblocking::CanvasFingerprintingPerTab2Label::eNotFound) - .Add(); + glean::contentblocking::canvas_fingerprinting_per_tab.EnumGet(label) + .AccumulateSingleSample(0); + if (aShouldReport) { + glean::contentblocking::canvas_fingerprinting_per_tab + .EnumGet(labelMatched) + .AccumulateSingleSample(0); + } } else { - // Increment the global 'did the page have any' metric - glean::contentblocking::canvas_fingerprinting_per_tab2 - .EnumGet( - glean::contentblocking::CanvasFingerprintingPerTab2Label::eFound) - .Add(); + int32_t fingerprinter = + canvasFingerprinter.isSome() ? (*canvasFingerprinter + 1) : 0; + auto label = + canvasFingerprinterKnownText + ? glean::contentblocking::CanvasFingerprintingPerTabLabel:: + eKnownText + : glean::contentblocking::CanvasFingerprintingPerTabLabel::eUnknown; + glean::contentblocking::canvas_fingerprinting_per_tab.EnumGet(label) + .AccumulateSingleSample(fingerprinter); + if (aShouldReport) { + glean::contentblocking::canvas_fingerprinting_per_tab + .EnumGet(labelMatched) + .AccumulateSingleSample(fingerprinter); + } } } @@ -495,7 +435,9 @@ ContentBlockingLog::OriginEntry* ContentBlockingLog::RecordLogInternal( const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, const nsTArray<nsCString>& aTrackingFullHashes, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { DebugOnly<bool> isCookiesBlockedTracker = aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; @@ -519,7 +461,8 @@ ContentBlockingLog::OriginEntry* ContentBlockingLog::RecordLogInternal( if (!entry.mData->mLogs.IsEmpty()) { auto& last = entry.mData->mLogs.LastElement(); if (last.mType == aType && last.mBlocked == aBlocked && - last.mCanvasFingerprintingEvent == aCanvasFingerprintingEvent) { + last.mCanvasFingerprinter == aCanvasFingerprinter && + last.mCanvasFingerprinterKnownText == aCanvasFingerprinterKnownText) { ++last.mRepeatCount; // Don't record recorded events. This helps compress our log. // We don't care about if the the reason is the same, just keep the @@ -540,16 +483,19 @@ ContentBlockingLog::OriginEntry* ContentBlockingLog::RecordLogInternal( // Cap the size at the maximum length adjustable by the pref entry.mData->mLogs.RemoveElementAt(0); } - entry.mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked, aReason, - aTrackingFullHashes.Clone(), - aCanvasFingerprintingEvent}); + entry.mData->mLogs.AppendElement( + LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(), + aCanvasFingerprinter, aCanvasFingerprinterKnownText}); // Check suspicious fingerprinting activities if the origin hasn't already // been marked. // TODO(Bug 1864909): Moving the suspicious fingerprinting detection call // out of here. - if (aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING || - aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) { + if ((aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING || + aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) && + !entry.mData->mHasSuspiciousFingerprintingActivity && + nsRFPService::CheckSuspiciousFingerprintingActivity( + entry.mData->mLogs)) { entry.mData->mHasSuspiciousFingerprintingActivity = true; } return &entry; @@ -579,16 +525,18 @@ ContentBlockingLog::OriginEntry* ContentBlockingLog::RecordLogInternal( MOZ_ASSERT(entry->mData->mHasSocialTrackerCookiesLoaded.isNothing()); entry->mData->mHasSocialTrackerCookiesLoaded.emplace(aBlocked); } else { - entry->mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked, aReason, - aTrackingFullHashes.Clone(), - aCanvasFingerprintingEvent}); + entry->mData->mLogs.AppendElement( + LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(), + aCanvasFingerprinter, aCanvasFingerprinterKnownText}); // Check suspicious fingerprinting activities if the origin hasn't been // marked. // TODO(Bug 1864909): Moving the suspicious fingerprinting detection call // out of here. - if (aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING || - aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) { + if ((aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING || + aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) && + nsRFPService::CheckSuspiciousFingerprintingActivity( + entry->mData->mLogs)) { entry->mData->mHasSuspiciousFingerprintingActivity = true; } } diff --git a/toolkit/components/antitracking/ContentBlockingLog.h b/toolkit/components/antitracking/ContentBlockingLog.h @@ -36,7 +36,8 @@ class ContentBlockingLog final { Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason> mReason; nsTArray<nsCString> mTrackingFullHashes; - Maybe<CanvasFingerprintingEvent> mCanvasFingerprintingEvent; + Maybe<ContentBlockingNotifier::CanvasFingerprinter> mCanvasFingerprinter; + Maybe<bool> mCanvasFingerprinterKnownText; }; struct OriginDataEntry { @@ -93,8 +94,9 @@ class ContentBlockingLog final { ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason = Nothing(), const nsTArray<nsCString>& aTrackingFullHashes = nsTArray<nsCString>(), - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent = - Nothing()); + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter = Nothing(), + const Maybe<bool> aCanvasFingerprinterKnownText = Nothing()); void RecordLog( const nsACString& aOrigin, uint32_t aType, bool aBlocked, @@ -106,7 +108,8 @@ class ContentBlockingLog final { } void ReportLog(); - void ReportCanvasFingerprintingLog(nsIPrincipal* aFirstPartyPrincipal); + void ReportCanvasFingerprintingLog(nsIPrincipal* aFirstPartyPrincipal, + bool aShouldReport); void ReportFontFingerprintingLog(nsIPrincipal* aFirstPartyPrincipal); void ReportEmailTrackingLog(nsIPrincipal* aFirstPartyPrincipal); @@ -264,8 +267,9 @@ class ContentBlockingLog final { ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason = Nothing(), const nsTArray<nsCString>& aTrackingFullHashes = nsTArray<nsCString>(), - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent = - Nothing()); + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter = Nothing(), + const Maybe<bool> aCanvasFingerprinterKnownText = Nothing()); bool RecordLogEntryInCustomField(uint32_t aType, OriginEntry& aEntry, bool aBlocked) { diff --git a/toolkit/components/antitracking/ContentBlockingNotifier.cpp b/toolkit/components/antitracking/ContentBlockingNotifier.cpp @@ -334,7 +334,9 @@ void NotifyEventInChild( const nsACString& aTrackingOrigin, const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter> + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { MOZ_ASSERT(XRE_IsContentProcess()); // We don't need to find the top-level window here because the @@ -364,7 +366,8 @@ void NotifyEventInChild( browserChild->NotifyContentBlockingEvent( aRejectedReason, aTrackingChannel, aBlocked, aTrackingOrigin, - trackingFullHashes, aReason, aCanvasFingerprintingEvent); + trackingFullHashes, aReason, aCanvasFingerprinter, + aCanvasFingerprinterKnownText); } // Update the ContentBlockingLog of the top-level WindowGlobalParent of @@ -374,7 +377,9 @@ void NotifyEventInParent( const nsACString& aTrackingOrigin, const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<ContentBlockingNotifier::CanvasFingerprinter> + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr<nsILoadInfo> loadInfo = aTrackingChannel->LoadInfo(); @@ -400,7 +405,8 @@ void NotifyEventInParent( wgp->NotifyContentBlockingEvent(aRejectedReason, aTrackingChannel, aBlocked, aTrackingOrigin, trackingFullHashes, aReason, - aCanvasFingerprintingEvent); + aCanvasFingerprinter, + aCanvasFingerprinterKnownText); } } // namespace @@ -570,12 +576,15 @@ void ContentBlockingNotifier::OnEvent( nsIChannel* aTrackingChannel, bool aBlocked, uint32_t aRejectedReason, const nsACString& aTrackingOrigin, const Maybe<StorageAccessPermissionGrantedReason>& aReason, - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent) { + const Maybe<CanvasFingerprinter>& aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { if (XRE_IsParentProcess()) { NotifyEventInParent(aTrackingChannel, aBlocked, aRejectedReason, - aTrackingOrigin, aReason, aCanvasFingerprintingEvent); + aTrackingOrigin, aReason, aCanvasFingerprinter, + aCanvasFingerprinterKnownText); } else { NotifyEventInChild(aTrackingChannel, aBlocked, aRejectedReason, - aTrackingOrigin, aReason, aCanvasFingerprintingEvent); + aTrackingOrigin, aReason, aCanvasFingerprinter, + aCanvasFingerprinterKnownText); } } diff --git a/toolkit/components/antitracking/ContentBlockingNotifier.h b/toolkit/components/antitracking/ContentBlockingNotifier.h @@ -9,7 +9,6 @@ #include "nsStringFwd.h" #include "mozilla/Maybe.h" -#include "nsRFPService.h" #define ANTITRACKING_CONSOLE_CATEGORY "Content Blocking"_ns @@ -35,6 +34,27 @@ class ContentBlockingNotifier final { ePrivilegeStorageAccessForOriginAPI, }; + // We try to classify observed canvas fingerprinting scripts into different + // classes, but we don't usually know the source/vendor of those scripts. The + // classification is based on a behavioral analysis, based on type of canvas, + // the extracted (e.g. toDataURL) size, the usage of functions like fillText + // etc. See `nsRFPService::MaybeReportCanvasFingerprinter` for the + // classification heuristic. + enum CanvasFingerprinter { + // Suspected fingerprint.com (FingerprintJS) + eFingerprintJS, + // Suspected Akamai fingerprinter + eAkamai, + // Unknown but distinct types of fingerprinters + eVariant1, + eVariant2, + eVariant3, + eVariant4, + // This just indicates that more than one canvas was extracted and is a + // very weak signal. + eMaybe + }; + // This method can be called on the parent process or on the content process. // The notification is propagated to the child channel if aChannel is a parent // channel proxy. @@ -65,8 +85,8 @@ class ContentBlockingNotifier final { const nsACString& aTrackingOrigin, const ::mozilla::Maybe<StorageAccessPermissionGrantedReason>& aReason = Nothing(), - const Maybe<CanvasFingerprintingEvent>& aCanvasFingerprintingEvent = - Nothing()); + const Maybe<CanvasFingerprinter>& aCanvasFingerprinter = Nothing(), + const Maybe<bool> aCanvasFingerprinterKnownText = Nothing()); static void ReportUnblockingToConsole( dom::BrowsingContext* aBrowsingContext, const nsAString& aTrackingOrigin, diff --git a/toolkit/components/antitracking/metrics.yaml b/toolkit/components/antitracking/metrics.yaml @@ -134,93 +134,6 @@ contentblocking: expires: 150 telemetry_mirror: CANVAS_FINGERPRINTING_PER_TAB - canvas_fingerprinting_per_tab2: - type: labeled_counter - description: > - Whether any canvas fingerprinting attempt was detected, as identified by either a known fingerprinting text or a known behavior (alias) - labels: - - not_found - - found - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1873716 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1847990 - notification_emails: - - seceng-telemetry@mozilla.com - expires: 165 - - canvas_fingerprinting_type_text_by_source_per_tab2: - type: dual_labeled_counter - description: Fingerprinting detection hits by known fingerprinting string (key) and source (category). - dual_labels: - key: - description: Known Fingerprinting Strings (exponent of the bit in the KnownText bitmask) - labels: ["none", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"] - category: - description: > - Canvas Extraction Sources. - The first set of labels are the powers of two indicating each individual bit that can be set in the bitmask. - The second set of labels are the combinations of sources seen or expected. - A combination of sources that is not listed will be recorded under the Glean-special label __other__ - labels: [ - # 2^0 - 2^8 - "1", "2", "4", "8", "16", "32", "64", "128", "256", - # 2^9 - 2^16 - "512", "1024", "2048", "4096", "8192", "16384", "32768", "65536", - # 2^17 - 2^24 - "131072", "262144", "524288", "1048576", "2097152", "4194304", "8388608", "16777216", - # 2^25 - 2^32 - "33554432", "67108864", "134217728", "268435456", "536870912", "1073741824", "2147483648", "4294967296", - # 2^33 - 2^40 - "8589934592", "17179869184", "34359738368", "68719476736", "137438953472", "274877906944", "549755813888", "1099511627776", - # Known Combinations - "80", "144", "208", "8388736" - ] - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1873716 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1847990 - notification_emails: - - seceng-telemetry@mozilla.com - expires: 165 - - canvas_fingerprinting_type_alias_by_source_per_tab2: - type: dual_labeled_counter - description: > - Fingerprinting detection hits by fingerprinter alias (key) and the source(s) used (category). - An alias using multiple sources will increment each source individually once, and increment the combination once as well. - dual_labels: - key: - description: A numeral corresponding to the CanvasFingerprinterAlias enum - labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50"] - category: - description: > - Canvas Extraction Sources. - The first set of labels are the powers of two indicating each individual bit that can be set in the bitmask. - The second set of labels are the combinations of sources seen or expected. - A combination of sources that is not listed will be recorded under the Glean-special label __other__ - labels: [ - # 2^0 - 2^8 - "1", "2", "4", "8", "16", "32", "64", "128", "256", - # 2^9 - 2^16 - "512", "1024", "2048", "4096", "8192", "16384", "32768", "65536", - # 2^17 - 2^24 - "131072", "262144", "524288", "1048576", "2097152", "4194304", "8388608", "16777216", - # 2^25 - 2^32 - "33554432", "67108864", "134217728", "268435456", "536870912", "1073741824", "2147483648", "4294967296", - # 2^33 - 2^40 - "8589934592", "17179869184", "34359738368", "68719476736", "137438953472", "274877906944", "549755813888", "1099511627776", - # Known Combinations - "80", "144", "208", "8388736" - ] - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id= - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id= - notification_emails: - - seceng-telemetry@mozilla.com - expires: 165 - font_fingerprinting_per_tab: type: labeled_counter description: > diff --git a/toolkit/components/resistfingerprinting/moz.build b/toolkit/components/resistfingerprinting/moz.build @@ -45,7 +45,6 @@ EXPORTS += [ "RFPTargets.inc", ] EXPORTS.mozilla += [ - "nsRFPIPCUtils.h", "RelativeTimeline.h", ] EXPORTS.mozilla.gtest += ["nsUserCharacteristics.h"] diff --git a/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h b/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h @@ -1,53 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 sw=2 et tw=80: */ -/* 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/. */ - -#ifndef mozilla_resistfingerprinting_nsRFPIPCUtils_h -#define mozilla_resistfingerprinting_nsRFPIPCUtils_h - -#include "ipc/IPCMessageUtils.h" -#include "ipc/EnumSerializer.h" -#include "nsRFPService.h" - -namespace IPC { - -// CanvasFingerprinterAlias -template <> -struct ParamTraits<mozilla::CanvasFingerprinterAlias> - : public ContiguousEnumSerializerInclusive< - mozilla::CanvasFingerprinterAlias, - mozilla::CanvasFingerprinterAlias::eNoneIdentified, - mozilla::CanvasFingerprinterAlias::eLastAlias> {}; - -// CanvasFingerprintingEvent -template <> -struct ParamTraits<mozilla::CanvasFingerprintingEvent> { - typedef mozilla::CanvasFingerprintingEvent paramType; - - static void Write(MessageWriter* aWriter, const paramType& aParam) { - WriteParam(aWriter, aParam.alias); - WriteParam(aWriter, aParam.knownTextBitmask); - WriteParam(aWriter, aParam.sourcesBitmask); - } - - static bool Read(MessageReader* aReader, paramType* aResult) { - mozilla::CanvasFingerprinterAlias alias; - uint32_t knownTextBitmask; - uint64_t sourcesBitmask; - - if (!ReadParam(aReader, &alias) || !ReadParam(aReader, &knownTextBitmask) || - !ReadParam(aReader, &sourcesBitmask)) { - return false; - } - - *aResult = mozilla::CanvasFingerprintingEvent(alias, knownTextBitmask, - sourcesBitmask); - return true; - } -}; - -} // namespace IPC - -#endif // mozilla_resistfingerprinting_nsRFPIPCUtils_h diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp @@ -44,8 +44,6 @@ #include "mozilla/TextEvents.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/CanvasRenderingContextHelper.h" -#include "mozilla/dom/CanvasRenderingContext2D.h" -#include "mozilla/dom/CanvasUtils.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" @@ -53,9 +51,9 @@ #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/MediaDeviceInfoBinding.h" #include "mozilla/dom/quota/QuotaManager.h" -#include "mozilla/gfx/2D.h" #include "mozilla/intl/LocaleService.h" #include "mozilla/XorShift128PlusRNG.h" +#include "mozilla/dom/CanvasUtils.h" #include "nsAboutProtocolUtils.h" #include "nsBaseHashtable.h" @@ -158,6 +156,8 @@ MOZ_RUNINIT const RFPTargetSet kDefaultFingerprintingProtections = { #undef DESKTOP_DEFAULT // NOLINTEND(bugprone-macro-parentheses) +static constexpr uint32_t kSuspiciousFingerprintingActivityThreshold = 1; + // ============================================================================ // ============================================================================ // ============================================================================ @@ -1731,66 +1731,16 @@ nsresult nsRFPService::GenerateCanvasKeyFromImageData( } // static -void nsRFPService::PotentiallyDumpImage(nsIPrincipal* aPrincipal, - gfx::DataSourceSurface* aSurface) { - // Only dump from the content process to avoid unintended file writes from - // privileged or background processes. - if (XRE_GetProcessType() != GeckoProcessType_Content) { - return; - } - if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Debug)) { - int32_t format = 0; - UniquePtr<uint8_t[]> imageBuffer = - gfxUtils::GetImageBuffer(aSurface, true, &format); - nsRFPService::PotentiallyDumpImage( - aPrincipal, imageBuffer.get(), aSurface->GetSize().width, - aSurface->GetSize().height, - aSurface->GetSize().width * aSurface->GetSize().height * 4); - } -} - -void nsRFPService::PotentiallyDumpImage(nsIPrincipal* aPrincipal, - uint8_t* aData, uint32_t aWidth, - uint32_t aHeight, uint32_t aSize) { - // Only dump from the content process to avoid unintended file writes from - // privileged or background processes. - if (XRE_GetProcessType() != GeckoProcessType_Content) { - return; - } - nsAutoCString safeSite; - - if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Debug)) { - nsAutoCString site; - if (aPrincipal) { - nsCOMPtr<nsIURI> uri = aPrincipal->GetURI(); - if (uri) { - site.Assign(uri->GetSpecOrDefault()); - } - } - if (site.IsEmpty()) { - site.AssignLiteral("unknown"); - } - - safeSite.SetCapacity(site.Length()); - // Build a sanitized site string from the principal's URI for filename. - // Allow alnum, '.', '_', '-', replace others with '_'. - for (uint32_t i = 0; i < site.Length() && safeSite.Length() < 80; ++i) { - char c = site[i]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-') { - safeSite.Append(c); - } else { - safeSite.Append('_'); - } - } - - MOZ_LOG(gFingerprinterDetection, LogLevel::Debug, - ("Would dump a canvas image from %s width: %i height: %i bytes: %i", - site.get(), aWidth, aHeight, aSize)); - } - - if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Verbose)) { - // Requires sandbox level 0 +nsresult nsRFPService::RandomizePixels(nsICookieJarSettings* aCookieJarSettings, + nsIPrincipal* aPrincipal, uint8_t* aData, + uint32_t aWidth, uint32_t aHeight, + uint32_t aSize, + gfx::SurfaceFormat aSurfaceFormat) { +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +#endif + if (false) { // For debugging purposes you can dump the image with this code // then convert it with the image-magick command // convert -size WxH -depth 8 rgba:$i $i.png @@ -1798,47 +1748,17 @@ void nsRFPService::PotentiallyDumpImage(nsIPrincipal* aPrincipal, // up... static int calls = 0; char filename[256]; - SprintfLiteral(filename, "rendered_image_%s__%dx%d_%d", safeSite.get(), - aWidth, aHeight, calls); - - const char* logEnv = PR_GetEnv("MOZ_LOG_FILE"); -#ifdef XP_WIN - const char sep = '\\'; -#else - const char sep = '/'; + SprintfLiteral(filename, "rendered_image_%dx%d_%d_pre", aWidth, aHeight, + calls); + FILE* outputFile = fopen(filename, "wb"); // "wb" for binary write mode + fwrite(aData, 1, aSize, outputFile); + fclose(outputFile); + calls++; + } +#ifdef __clang__ +# pragma clang diagnostic pop #endif - const char* dirEnd = nullptr; - if (logEnv) { - for (const char* it = logEnv; *it; ++it) { - if (*it == sep) { - dirEnd = it; - } - } - } - char outPath[512]; - if (dirEnd) { - int dirLen = int(dirEnd - logEnv + 1); - SprintfLiteral(outPath, "%.*s%s", dirLen, logEnv, filename); - } else { - SprintfLiteral(outPath, "%s", filename); - } - - FILE* outputFile = fopen(outPath, "wb"); - if (outputFile) { - fwrite(aData, 1, aSize, outputFile); - fclose(outputFile); - calls++; - } - } -} - -// static -nsresult nsRFPService::RandomizePixels(nsICookieJarSettings* aCookieJarSettings, - nsIPrincipal* aPrincipal, uint8_t* aData, - uint32_t aWidth, uint32_t aHeight, - uint32_t aSize, - gfx::SurfaceFormat aSurfaceFormat) { constexpr uint8_t bytesPerChannel = 1; constexpr uint8_t bytesPerPixel = 4 * bytesPerChannel; @@ -1994,377 +1914,24 @@ nsresult nsRFPService::RandomizeElements( } static const char* CanvasFingerprinterToString( - CanvasFingerprinterAlias aFingerprinter) { + ContentBlockingNotifier::CanvasFingerprinter aFingerprinter) { switch (aFingerprinter) { - case CanvasFingerprinterAlias::eNoneIdentified: - return "(None Identified)"; - case CanvasFingerprinterAlias::eFingerprintJS: + case ContentBlockingNotifier::CanvasFingerprinter::eFingerprintJS: return "FingerprintJS"; - case CanvasFingerprinterAlias::eAkamai: + case ContentBlockingNotifier::CanvasFingerprinter::eAkamai: return "Akamai"; - case CanvasFingerprinterAlias::eOzoki: - return "Ozoki"; - case CanvasFingerprinterAlias::ePerimeterX: - return "PerimeterX"; - case CanvasFingerprinterAlias::eSignifyd: - return "Signifyd"; - case CanvasFingerprinterAlias::eClaydar: - return "Claydar"; - case CanvasFingerprinterAlias::eForter: - return "Forter"; - case CanvasFingerprinterAlias::eVariant1: + case ContentBlockingNotifier::CanvasFingerprinter::eVariant1: return "Variant1"; - case CanvasFingerprinterAlias::eVariant2: + case ContentBlockingNotifier::CanvasFingerprinter::eVariant2: return "Variant2"; - case CanvasFingerprinterAlias::eVariant3: + case ContentBlockingNotifier::CanvasFingerprinter::eVariant3: return "Variant3"; - case CanvasFingerprinterAlias::eVariant4: + case ContentBlockingNotifier::CanvasFingerprinter::eVariant4: return "Variant4"; - case CanvasFingerprinterAlias::eVariant5: - return "Variant5"; - case CanvasFingerprinterAlias::eVariant6: - return "Variant6"; - case CanvasFingerprinterAlias::eVariant7: - return "Variant7"; - case CanvasFingerprinterAlias::eVariant8: - return "Variant8"; - } - MOZ_ASSERT(false, "Unhandled CanvasFingerprinterAlias enum value"); - 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 (aContext && (aContextType == dom::CanvasContextType::Canvas2D || - aContextType == dom::CanvasContextType::OffscreenCanvas2D)) { - // 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; + case ContentBlockingNotifier::CanvasFingerprinter::eMaybe: + return "Maybe"; } + return "<error>"; } static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, @@ -2387,159 +1954,121 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, } /* static */ void nsRFPService::MaybeReportCanvasFingerprinter( - nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, const nsACString& aURI, - const nsACString& aOriginNoSuffix) { + nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, + nsACString& aOriginNoSuffix) { if (!aChannel) { return; } - bool extractedWebGL = false; + uint32_t extractedWebGL = 0; bool seenExtractedWebGL_300x150 = false; - bool seenExtractedWebGL_2000x200 = false; uint32_t extracted2D = 0; + bool seenExtracted2D_16x16 = false; bool seenExtracted2D_122x110 = false; - bool seenExtracted2D_220x30 = false; bool seenExtracted2D_240x60 = false; - bool seenExtracted2D_250x80 = false; - bool seenExtracted2D_300x100 = false; - bool seenExtracted2D_650x12 = false; + bool seenExtracted2D_280x60 = false; bool seenExtracted2D_860x6 = false; + CanvasFeatureUsage featureUsage = CanvasFeatureUsage::None; - CanvasFeatureUsage accumulatedFeatureUsage = CanvasFeatureUsage::None; - CanvasUsageSource accumulatedUsageSource = CanvasUsageSource::Unknown; - - MOZ_LOG( - gFingerprinterDetection, LogLevel::Debug, - ("MaybeReportCanvasFingerprinter: examining %zu uses", aUses.Length())); + uint32_t extractedOther = 0; for (const auto& usage : aUses) { int32_t width = usage.mSize.width; int32_t height = usage.mSize.height; - if (width > 2500 || height > 1000) { + if (width > 2000 || height > 1000) { // Canvases used for fingerprinting are usually relatively small. continue; } - accumulatedFeatureUsage |= usage.mFeatureUsage; - accumulatedUsageSource |= usage.mUsageSource; - - if (usage.mType == dom::CanvasContextType::Canvas2D || - usage.mType == dom::CanvasContextType::OffscreenCanvas2D) { - accumulatedFeatureUsage |= usage.mFeatureUsage; + if (usage.mType == dom::CanvasContextType::Canvas2D) { + featureUsage |= usage.mFeatureUsage; extracted2D++; - if (width == 122 && height == 110) { - seenExtracted2D_122x110 = true; - } else if (width == 220 && height == 30) { - seenExtracted2D_220x30 = true; + if (width == 16 && height == 16) { + seenExtracted2D_16x16 = true; } else if (width == 240 && height == 60) { seenExtracted2D_240x60 = true; - } else if (width == 250 && height == 80) { - seenExtracted2D_250x80 = true; - } else if (width == 300 && height == 100) { - seenExtracted2D_300x100 = true; - } else if (width == 650 && height == 12) { - seenExtracted2D_650x12 = true; + } else if (width == 122 && height == 110) { + seenExtracted2D_122x110 = true; + } else if (width == 280 && height == 60) { + seenExtracted2D_280x60 = true; } else if (width == 860 && height == 6) { seenExtracted2D_860x6 = true; } } else if (usage.mType == dom::CanvasContextType::WebGL1) { - extractedWebGL = true; + extractedWebGL++; if (width == 300 && height == 150) { seenExtractedWebGL_300x150 = true; - } else if (width == 2000 && height == 200) { - seenExtractedWebGL_2000x200 = true; } + } else { + extractedOther++; } } - CanvasFingerprinterAlias fingerprinter = eNoneIdentified; - uint32_t knownTextBitmask = static_cast<uint32_t>( - static_cast<uint64_t>(accumulatedFeatureUsage) & 0xFFFFFFFFu); - + Maybe<ContentBlockingNotifier::CanvasFingerprinter> fingerprinter; if (seenExtractedWebGL_300x150 && seenExtracted2D_240x60 && seenExtracted2D_122x110) { - fingerprinter = CanvasFingerprinterAlias::eFingerprintJS; - } else if (seenExtractedWebGL_300x150 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_12 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_13) { - fingerprinter = CanvasFingerprinterAlias::eAkamai; - } else if (accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_9 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_10 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_11) { - fingerprinter = CanvasFingerprinterAlias::eOzoki; - } else if (seenExtractedWebGL_2000x200 && knownTextBitmask == 0) { - fingerprinter = CanvasFingerprinterAlias::ePerimeterX; - } else if (seenExtracted2D_220x30 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_7) { - fingerprinter = CanvasFingerprinterAlias::eSignifyd; - } else if (seenExtracted2D_300x100 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_8) { - fingerprinter = CanvasFingerprinterAlias::eSignifyd; - } else if (seenExtracted2D_240x60 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_4) { - fingerprinter = CanvasFingerprinterAlias::eClaydar; - } else if (accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_23) { - fingerprinter = CanvasFingerprinterAlias::eForter; - } else if (seenExtracted2D_250x80 && - accumulatedFeatureUsage & CanvasFeatureUsage::KnownText_6) { - fingerprinter = CanvasFingerprinterAlias::eVariant5; - } else if (seenExtracted2D_650x12) { - fingerprinter = CanvasFingerprinterAlias::eVariant6; - } else if (seenExtracted2D_860x6) { - fingerprinter = CanvasFingerprinterAlias::eVariant7; + fingerprinter = + Some(ContentBlockingNotifier::CanvasFingerprinter::eFingerprintJS); + } else if (seenExtractedWebGL_300x150 && seenExtracted2D_280x60 && + seenExtracted2D_16x16) { + fingerprinter = Some(ContentBlockingNotifier::CanvasFingerprinter::eAkamai); } else if (seenExtractedWebGL_300x150 && extracted2D > 0 && - (accumulatedFeatureUsage & CanvasFeatureUsage::SetFont)) { - fingerprinter = CanvasFingerprinterAlias::eVariant1; + (featureUsage & CanvasFeatureUsage::SetFont)) { + fingerprinter = + Some(ContentBlockingNotifier::CanvasFingerprinter::eVariant1); } else if (extractedWebGL > 0 && extracted2D > 1 && seenExtracted2D_860x6) { - fingerprinter = CanvasFingerprinterAlias::eVariant2; - } else if (extractedWebGL > 0 || extracted2D > 0) { - fingerprinter = CanvasFingerprinterAlias::eVariant3; - } else if (extracted2D > 0 && - (accumulatedFeatureUsage & CanvasFeatureUsage::SetFont) && - (accumulatedFeatureUsage & + fingerprinter = + Some(ContentBlockingNotifier::CanvasFingerprinter::eVariant2); + } else if (extractedOther > 0 && (extractedWebGL > 0 || extracted2D > 0)) { + fingerprinter = + Some(ContentBlockingNotifier::CanvasFingerprinter::eVariant3); + } else if (extracted2D > 0 && (featureUsage & CanvasFeatureUsage::SetFont) && + (featureUsage & (CanvasFeatureUsage::FillRect | CanvasFeatureUsage::LineTo | CanvasFeatureUsage::Stroke))) { - fingerprinter = CanvasFingerprinterAlias::eVariant4; + fingerprinter = + Some(ContentBlockingNotifier::CanvasFingerprinter::eVariant4); + } else if (extractedOther + extractedWebGL + extracted2D > 1) { + // This I added primarily to not miss anything, but it can cause false + // positives. + fingerprinter = Some(ContentBlockingNotifier::CanvasFingerprinter::eMaybe); + } + + bool knownFingerprintText = + bool(featureUsage & CanvasFeatureUsage::KnownFingerprintText); + if (!knownFingerprintText && fingerprinter.isNothing()) { + return; } - nsAutoCString uri(aURI); - nsAutoCString origin(aOriginNoSuffix); - nsAutoCString filename; if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Info)) { + nsAutoCString filename; uint32_t lineNum = 0; uint32_t columnNum = 0; MaybeCurrentCaller(filename, lineNum, columnNum); - } - if (knownTextBitmask == 0 && fingerprinter == eNoneIdentified) { - MOZ_LOG(gFingerprinterDetection, LogLevel::Debug, - ("Found no potential canvas fingerprinter on %s on %s in script %s", - origin.get(), uri.get(), filename.get())); - return; + nsAutoCString origin(aOriginNoSuffix); + MOZ_LOG( + gFingerprinterDetection, LogLevel::Info, + ("Detected a potential canvas fingerprinter on %s in script %s:%d:%d " + "(KnownFingerprintText: %s, CanvasFingerprinter: %s)", + origin.get(), filename.get(), lineNum, columnNum, + knownFingerprintText ? "true" : "false", + fingerprinter.isSome() + ? CanvasFingerprinterToString(fingerprinter.value()) + : "<none>")); } - auto event = CanvasFingerprintingEvent(fingerprinter, knownTextBitmask, - accumulatedUsageSource); - - MOZ_LOG(gFingerprinterDetection, LogLevel::Info, - ("Detected a potential canvas fingerprinter on %s on %s in script %s " - "(KnownFingerprintTextBitmask: %u, CanvasFingerprinterAlias: %s, " - "AccumulatedCanvasUsageSource: %s)", - origin.get(), uri.get(), filename.get(), knownTextBitmask, - CanvasFingerprinterToString(fingerprinter), - CanvasUsageSourceToString(accumulatedUsageSource).get())); - ContentBlockingNotifier::OnEvent( aChannel, false, - nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING, origin, - Nothing(), Some(event)); + nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING, + aOriginNoSuffix, Nothing(), fingerprinter, + Some(featureUsage & CanvasFeatureUsage::KnownFingerprintText)); } /* static */ void nsRFPService::MaybeReportFontFingerprinter( - nsIChannel* aChannel, const nsACString& aURI, - const nsACString& aOriginNoSuffix) { + nsIChannel* aChannel, const nsACString& aOriginNoSuffix) { if (!aChannel) { return; } @@ -2551,32 +2080,71 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum, NS_DispatchToMainThread(NS_NewRunnableFunction( "nsRFPService::MaybeReportFontFingerprinter", [channel = nsCOMPtr{aChannel}, - originNoSuffix = nsCString(aOriginNoSuffix), uri = nsCString(aURI)]() { - nsRFPService::MaybeReportFontFingerprinter(channel, uri, - originNoSuffix); + originNoSuffix = nsCString(aOriginNoSuffix)]() { + nsRFPService::MaybeReportFontFingerprinter(channel, 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 on %s in script " - "%s:%d:%d", - origin.get(), uri.get(), filename.get(), lineNum, columnNum)); + ("Detected a potential font fingerprinter on %s in script %s:%d:%d", + origin.get(), filename.get(), lineNum, columnNum)); } ContentBlockingNotifier::OnEvent( aChannel, false, - nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING, origin); + nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING, + aOriginNoSuffix); +} + +/* static */ +bool nsRFPService::CheckSuspiciousFingerprintingActivity( + nsTArray<ContentBlockingLog::LogEntry>& aLogs) { + if (aLogs.Length() == 0) { + return false; + } + + uint32_t cnt = 0; + // We use these two booleans to prevent counting duplicated fingerprinting + // events. + bool foundCanvas = false; + bool foundFont = false; + + // Iterate through the logs to see if there are suspicious fingerprinting + // activities. + for (auto& log : aLogs) { + // If it's a known canvas fingerprinter, we can directly return true from + // here. + if (log.mCanvasFingerprinter && + (log.mCanvasFingerprinter.ref() == + ContentBlockingNotifier::CanvasFingerprinter::eFingerprintJS || + log.mCanvasFingerprinter.ref() == + ContentBlockingNotifier::CanvasFingerprinter::eAkamai)) { + return true; + } else if (!foundCanvas && log.mType == + nsIWebProgressListener:: + STATE_ALLOWED_CANVAS_FINGERPRINTING) { + cnt++; + foundCanvas = true; + } else if (!foundFont && + log.mType == + nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) { + cnt++; + foundFont = true; + } + } + + // If the number of suspicious fingerprinting activity exceeds the threshold, + // we return true to indicates there is a suspicious fingerprinting activity. + return cnt > kSuspiciousFingerprintingActivityThreshold; } /* static */ diff --git a/toolkit/components/resistfingerprinting/nsRFPService.h b/toolkit/components/resistfingerprinting/nsRFPService.h @@ -11,6 +11,7 @@ #include "ErrorList.h" #include "PLDHashTable.h" #include "mozilla/BasicEvents.h" +#include "mozilla/ContentBlockingLog.h" #include "mozilla/gfx/Types.h" #include "mozilla/TypedEnumBits.h" #include "mozilla/dom/MediaDeviceInfoBinding.h" @@ -23,7 +24,6 @@ #include "nsISupports.h" #include "nsIRFPService.h" #include "nsStringFwd.h" -#include <queue> // Defines regarding spoofed values of Navigator object. These spoofed values // are returned when 'privacy.resistFingerprinting' is true. @@ -70,8 +70,6 @@ struct JSContext; class nsIChannel; -class nsICanvasRenderingContextInternal; - namespace mozilla { class WidgetKeyboardEvent; class OriginAttributes; @@ -80,9 +78,6 @@ namespace dom { class Document; enum class CanvasContextType : uint8_t; } // namespace dom -namespace gfx { -class DataSourceSurface; -} // namespace gfx enum KeyboardLang { EN = 0x01 }; @@ -170,190 +165,25 @@ enum TimerPrecisionType { // ============================================================================ -enum class CanvasFeatureUsage : uint64_t { +enum class CanvasFeatureUsage : uint8_t { None = 0, - - KnownText_1 = 1llu << 0, - KnownText_2 = 1llu << 1, - KnownText_3 = 1llu << 2, - KnownText_4 = 1llu << 3, - KnownText_5 = 1llu << 4, - KnownText_6 = 1llu << 5, - KnownText_7 = 1llu << 6, - KnownText_8 = 1llu << 7, - KnownText_9 = 1llu << 8, - KnownText_10 = 1llu << 9, - KnownText_11 = 1llu << 10, - KnownText_12 = 1llu << 11, - KnownText_13 = 1llu << 12, - KnownText_14 = 1llu << 13, - KnownText_15 = 1llu << 14, - KnownText_16 = 1llu << 15, - KnownText_17 = 1llu << 16, - KnownText_18 = 1llu << 17, - KnownText_19 = 1llu << 18, - KnownText_20 = 1llu << 19, - KnownText_21 = 1llu << 20, - KnownText_22 = 1llu << 21, - KnownText_23 = 1llu << 22, - KnownText_24 = 1llu << 23, - KnownText_25 = 1llu << 24, - KnownText_26 = 1llu << 25, - KnownText_27 = 1llu << 26, - KnownText_28 = 1llu << 27, - KnownText_29 = 1llu << 28, - KnownText_30 = 1llu << 29, - KnownText_31 = 1llu << 30, - KnownText_32 = 1llu << 31, - - SetFont = 1llu << 32, - FillRect = 1llu << 33, - LineTo = 1llu << 34, - Stroke = 1llu << 35, + KnownFingerprintText = 1 << 0, + SetFont = 1 << 1, + FillRect = 1 << 2, + LineTo = 1 << 3, + Stroke = 1 << 4 }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CanvasFeatureUsage); -// We try to classify observed canvas fingerprinting scripts into different -// classes, but we don't usually know the source/vendor of those scripts. The -// classification is based on a behavioral analysis covering things like the -// functions called and size of the canvas. The alias given is a guess and -// should not be considered definitive. The entity identified may not be doing -// this behavior at all, they may be doing a diferent or additional behaviors. -enum CanvasFingerprinterAlias { - eNoneIdentified = 0, - eFingerprintJS = 1, - eAkamai = 2, - eOzoki = 3, - ePerimeterX = 4, - eSignifyd = 5, - eClaydar = 6, - eForter = 7, - // Unknown but distinct types of fingerprinters - eVariant1 = 8, - eVariant2 = 9, - eVariant3 = 10, - eVariant4 = 11, - eVariant5 = 12, - eVariant6 = 13, - eVariant7 = 14, - eVariant8 = 15, - 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, - 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. - CanvasFingerprinterAlias alias; - // A bitmask of all of the known canvas fingerprinting texts - // non-zero indicates some known fingerprinting text was used, making the - // event highly likely to be fingerprinting. - uint32_t knownTextBitmask; - // A bitmap of all the sources that were used to extract canvas data - uint64_t sourcesBitmask; - - CanvasFingerprintingEvent() - : alias(CanvasFingerprinterAlias::eNoneIdentified), - knownTextBitmask(0), - sourcesBitmask(0) {} - - CanvasFingerprintingEvent(CanvasFingerprinterAlias aAlias, - 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 && - sourcesBitmask == other.sourcesBitmask; - } + CanvasFeatureUsage aFeatureUsage) + : mSize(aSize), mType(aType), mFeatureUsage(aFeatureUsage) {} }; // ============================================================================ @@ -514,12 +344,6 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { nsIURI* aFirstPartyURI, nsIPrincipal* aPrincipal, bool aForeignByAncestorContext); - static void PotentiallyDumpImage(nsIPrincipal* aPrincipal, - gfx::DataSourceSurface* aSurface); - static void PotentiallyDumpImage(nsIPrincipal* aPrincipal, uint8_t* aData, - uint32_t aWidth, uint32_t aHeight, - uint32_t aSize); - // This function is plumbed to RandomizeElements function. static nsresult RandomizePixels(nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal, uint8_t* aData, @@ -559,15 +383,19 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static void MaybeReportCanvasFingerprinter(nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, - const nsACString& aURI, - const nsACString& aOriginNoSuffix); + nsACString& aOriginNoSuffix); static void MaybeReportFontFingerprinter(nsIChannel* aChannel, - const nsACString& aURI, const nsACString& aOriginNoSuffix); // -------------------------------------------------------------------------- + // A helper function to check if there is a suspicious fingerprinting + // activity from given content blocking origin logs. It returns true if we + // detect suspicious fingerprinting activities. + static bool CheckSuspiciousFingerprintingActivity( + nsTArray<ContentBlockingLog::LogEntry>& aLogs); + // Generates a fake media device name with given kind and index. // Example: Internal Microphone static void GetMediaDeviceName(nsString& aName, diff --git a/toolkit/components/resistfingerprinting/tests/browser/browser_canvas_fingerprinter_telemetry.js b/toolkit/components/resistfingerprinting/tests/browser/browser_canvas_fingerprinter_telemetry.js @@ -12,35 +12,52 @@ const TEST_PATH = getRootDirectory(gTestPath).replace( const TEST_PAGE_NORMAL = TEST_PATH + "empty.html"; const TEST_PAGE_FINGERPRINTER = TEST_PATH + "canvas-fingerprinter.html"; -// Glean labeled counter metric name -const GLEAN_METRIC = "canvas_fingerprinting_per_tab2"; +const TELEMETRY_CANVAS_FINGERPRINTING_PER_TAB = "CANVAS_FINGERPRINTING_PER_TAB"; -const KEY_UNKNOWN = "not_found"; -const KEY_KNOWN_TEXT = "found"; +const KEY_UNKNOWN = "unknown"; +const KEY_KNOWN_TEXT = "known_text"; async function clearTelemetry() { - // Clear Glean metrics between tests to ensure isolation. - // The test-only clearing API is exposed via FOG in Firefox. - // This clears all metrics; acceptable for test isolation. - Services.fog.testResetFOG(); + Services.telemetry.getSnapshotForHistograms("main", true /* clear */); + Services.telemetry + .getKeyedHistogramById(TELEMETRY_CANVAS_FINGERPRINTING_PER_TAB) + .clear(); } -async function getLabeledCounter(metricName, label, checkCntFn) { - // Wait until the labeled counter appears with a value. - let value = 0; +async function getKeyedHistogram(histogram_id, key, bucket, checkCntFn) { + let histogram; + + // Wait until the telemetry probe appears. await TestUtils.waitForCondition(() => { - value = - Glean.contentblocking.canvasFingerprintingPerTab2[label].testGetValue(); - return checkCntFn ? checkCntFn(value) : value > 0; + let histograms = Services.telemetry.getSnapshotForKeyedHistograms( + "main", + false /* clear */ + ).parent; + + histogram = histograms[histogram_id]; + + let checkRes = false; + + if (histogram && histogram[key]) { + checkRes = checkCntFn ? checkCntFn(histogram[key].values[bucket]) : true; + } + + return checkRes; }); - return value; + + return histogram[key].values[bucket] || 0; } -async function checkLabeledCounter(metricName, label, expectedCnt) { - let cnt = await getLabeledCounter(metricName, label, v => { - return (v ?? 0) == expectedCnt; +async function checkKeyedHistogram(histogram_id, key, bucket, expectedCnt) { + let cnt = await getKeyedHistogram(histogram_id, key, bucket, cnt => { + if (cnt === undefined) { + cnt = 0; + } + + return cnt == expectedCnt; }); - is(cnt, expectedCnt, "Expected count in Glean labeled counter."); + + is(cnt, expectedCnt, "There should be expected count in keyed telemetry."); } add_setup(async function () { @@ -60,7 +77,12 @@ add_task(async function test_canvas_fingerprinting_telemetry() { // Check that the telemetry has been record properly for normal page. The // telemetry should show there was no known fingerprinting attempt. - await checkLabeledCounter(GLEAN_METRIC, KEY_UNKNOWN, 1); + await checkKeyedHistogram( + TELEMETRY_CANVAS_FINGERPRINTING_PER_TAB, + KEY_UNKNOWN, + 0, + 1 + ); await clearTelemetry(); }); @@ -78,8 +100,12 @@ add_task(async function test_canvas_fingerprinting_telemetry() { // The telemetry should show a a known fingerprinting text and a known // canvas fingerprinter. - // CanvasFingerprinter::eVariant4 encoded under label KEY_KNOWN_TEXT - await checkLabeledCounter(GLEAN_METRIC, KEY_KNOWN_TEXT, 1); + await checkKeyedHistogram( + TELEMETRY_CANVAS_FINGERPRINTING_PER_TAB, + KEY_KNOWN_TEXT, + 6, // CanvasFingerprinter::eVariant4 + 1 + ); await clearTelemetry(); }); diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm @@ -79,7 +79,6 @@ #include "mozilla/PresShell.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_apz.h" -#include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/StaticPrefs_general.h" #include "mozilla/StaticPrefs_ui.h"