tor-browser

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

commit 27fdfda522bbf9b237630f4a4f517540b6826f68
parent 7e5eaaabc7b4ed3d23f8732f3ca93ed8057b283a
Author: Tom Ritter <tom@mozilla.com>
Date:   Mon,  6 Oct 2025 02:09:45 +0000

Bug 1980264: Wire up an alternate efficient randomization layer r=timhuang

In the RFPService we expose functions that let us get the
Randomization Key for a context, and then later to take that
key and mix it with the existing image data hash.

At the hottest paths for image randomization, switch from using
the old randomization to the new, more efficient version.

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

Diffstat:
Mdom/canvas/CanvasRenderingContextHelper.cpp | 13+++++++++++++
Mdom/canvas/CanvasRenderingContextHelper.h | 3+++
Mdom/canvas/CanvasUtils.cpp | 7+++++++
Mdom/canvas/CanvasUtils.h | 1+
Mdom/html/HTMLCanvasElement.cpp | 5+++++
Mtoolkit/components/resistfingerprinting/RFPTargets.inc | 2++
Mtoolkit/components/resistfingerprinting/nsRFPService.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtoolkit/components/resistfingerprinting/nsRFPService.h | 8++++++++
8 files changed, 105 insertions(+), 2 deletions(-)

diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp @@ -63,6 +63,11 @@ void CanvasRenderingContextHelper::ToBlob( } nsCString randomizationKeyStr = VoidCString(); + if (aExtractionBehavior == CanvasUtils::ImageExtraction::EfficientRandomize) { + nsRFPService::GetFingerprintingRandomizationKeyAsString( + GetCookieJarSettings(), randomizationKeyStr); + } + int32_t format = 0; auto imageSize = gfx::IntSize{elementSize.width, elementSize.height}; UniquePtr<uint8_t[]> imageBuffer = @@ -85,6 +90,14 @@ UniquePtr<uint8_t[]> CanvasRenderingContextHelper::GetImageBuffer( return nullptr; } +nsICookieJarSettings* CanvasRenderingContextHelper::GetCookieJarSettings() + const { + if (mCurrentContext) { + return mCurrentContext->GetCookieJarSettings(); + } + return nullptr; +} + already_AddRefed<nsICanvasRenderingContextInternal> CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) { return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE); diff --git a/dom/canvas/CanvasRenderingContextHelper.h b/dom/canvas/CanvasRenderingContextHelper.h @@ -13,6 +13,7 @@ #include "nsSize.h" class nsICanvasRenderingContextInternal; +class nsICookieJarSettings; class nsIGlobalObject; namespace mozilla { @@ -75,6 +76,8 @@ class CanvasRenderingContextHelper { CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat, gfx::IntSize* aOutImageSize); + nsICookieJarSettings* GetCookieJarSettings() const; + already_AddRefed<nsISupports> GetOrCreateContext( JSContext* aCx, const nsAString& aContextId, JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv); diff --git a/dom/canvas/CanvasUtils.cpp b/dom/canvas/CanvasUtils.cpp @@ -383,6 +383,13 @@ ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement, return ImageExtraction::Placeholder; } + if (ownerDoc->ShouldResistFingerprinting( + RFPTarget::EfficientCanvasRandomization) && + GetCanvasExtractDataPermission(aPrincipal) != + nsIPermissionManager::ALLOW_ACTION) { + return ImageExtraction::EfficientRandomize; + } + if ((ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) || ownerDoc->ShouldResistFingerprinting(RFPTarget::WebGLRandomization)) && GetCanvasExtractDataPermission(aPrincipal) != diff --git a/dom/canvas/CanvasUtils.h b/dom/canvas/CanvasUtils.h @@ -75,6 +75,7 @@ enum class ImageExtraction { Unrestricted, Placeholder, Randomize, + EfficientRandomize, }; // Returns whether the result of an image extraction should be replaced diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp @@ -942,6 +942,11 @@ nsresult HTMLCanvasElement::ExtractData(JSContext* aCx, } nsCString randomizationKey = VoidCString(); + if (extractionBehaviour == CanvasUtils::ImageExtraction::EfficientRandomize) { + nsRFPService::GetFingerprintingRandomizationKeyAsString( + GetCookieJarSettings(), randomizationKey); + } + return ImageEncoder::ExtractData(aType, aOptions, GetSize(), extractionBehaviour, randomizationKey, mCurrentContext, mOffscreenDisplay, aStream); diff --git a/toolkit/components/resistfingerprinting/RFPTargets.inc b/toolkit/components/resistfingerprinting/RFPTargets.inc @@ -24,6 +24,7 @@ ITEM_VALUE(CanvasRandomization, 9) ITEM_VALUE(CanvasImageExtractionPrompt, 10) ITEM_VALUE(CanvasExtractionFromThirdPartiesIsBlocked, 11) ITEM_VALUE(CanvasExtractionBeforeUserInputIsBlocked, 12) +// See also EfficientCanvasRandomization = 76 below ITEM_VALUE(JSLocale, 13) @@ -106,6 +107,7 @@ ITEM_VALUE(MaxTouchPoints, 72) ITEM_VALUE(MaxTouchPointsCollapse, 73) ITEM_VALUE(NavigatorHWConcurrencyTiered,74) ITEM_VALUE(WebGLRandomization, 75) +ITEM_VALUE(EfficientCanvasRandomization, 76) /* diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp @@ -1346,7 +1346,10 @@ nsresult nsRFPService::GetBrowsingSessionKey( RFPTarget::CanvasRandomization) && !nsContentUtils::ShouldResistFingerprinting( "Checking the target activation globally without local context", - RFPTarget::WebGLRandomization)) { + RFPTarget::WebGLRandomization) && + !nsContentUtils::ShouldResistFingerprinting( + "Checking the target activation globally without local context", + RFPTarget::EfficientCanvasRandomization)) { return NS_ERROR_NOT_AVAILABLE; } @@ -1448,7 +1451,9 @@ Maybe<nsTArray<uint8_t>> nsRFPService::GenerateKey(nsIChannel* aChannel) { if (!nsContentUtils::ShouldResistFingerprinting( aChannel, RFPTarget::CanvasRandomization) && !nsContentUtils::ShouldResistFingerprinting( - aChannel, RFPTarget::WebGLRandomization)) { + aChannel, RFPTarget::WebGLRandomization) && + !nsContentUtils::ShouldResistFingerprinting( + aChannel, RFPTarget::EfficientCanvasRandomization)) { return Nothing(); } auto sessionKeyStr = sessionKey.ToString(); @@ -2820,6 +2825,65 @@ uint32_t nsRFPService::CollapseMaxTouchPoints(uint32_t aMaxTouchPoints) { } /* static */ +void nsRFPService::GetFingerprintingRandomizationKeyAsString( + nsICookieJarSettings* aCookieJarSettings, + nsACString& aRandomizationKeyStr) { + NS_ENSURE_TRUE_VOID(aCookieJarSettings); + + nsTArray<uint8_t> randomizationKey(32); + nsresult rv = + aCookieJarSettings->GetFingerprintingRandomizationKey(randomizationKey); + NS_ENSURE_SUCCESS_VOID(rv); + + aRandomizationKeyStr.Assign( + reinterpret_cast<const char*>(randomizationKey.Elements()), + randomizationKey.Length()); +} + +/* static */ +nsresult nsRFPService::GenerateRandomizationKeyFromHash( + const nsACString& aRandomizationKeyStr, uint32_t aContentHash, + nsACString& aHex) { + MOZ_ASSERT(aHex.IsEmpty(), "aHex should be empty"); + + if (aRandomizationKeyStr.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + uint64_t k0 = *reinterpret_cast<const uint64_t*>(aRandomizationKeyStr.Data()); + uint64_t k1 = + *reinterpret_cast<const uint64_t*>(aRandomizationKeyStr.Data() + 8); + mozilla::HashCodeScrambler hcs(k0, k1); + mozilla::HashNumber hashResult = hcs.scramble(aContentHash); + + nsTArray<uint8_t> bufferKey(32); + + uint64_t digest = static_cast<uint64_t>(hashResult) << 32 | aContentHash; + non_crypto::XorShift128PlusRNG rng( + digest, + *reinterpret_cast<const uint64_t*>(aRandomizationKeyStr.Data() + 16)); + + for (size_t i = 0; i < 4; ++i) { + uint64_t val = rng.next(); + for (size_t j = 0; j < 8; ++j) { + uint8_t data = static_cast<uint8_t>((val >> (j * 8)) & 0xFF); + bufferKey.InsertElementAt((i * 8) + j, data); + } + } + + non_crypto::XorShift128PlusRNG rng1( + *reinterpret_cast<uint64_t*>(bufferKey.Elements()), + *reinterpret_cast<uint64_t*>(bufferKey.Elements() + 8)); + + uint64_t rand = rng1.next(); + + aHex.AppendPrintf("%016" PRIX64, rand); + MOZ_ASSERT(aHex.Length() == 16, "Expected 16 hex characters"); + + return NS_OK; +} + +/* static */ void nsRFPService::CalculateFontLocaleAllowlist() { static bool sAcceptLanguagesIsDirty = true; diff --git a/toolkit/components/resistfingerprinting/nsRFPService.h b/toolkit/components/resistfingerprinting/nsRFPService.h @@ -442,6 +442,14 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static Maybe<RFPTarget> TextToRFPTarget(const nsAString& aText); + static void GetFingerprintingRandomizationKeyAsString( + nsICookieJarSettings* aCookieJarSettings, + nsACString& aRandomizationKeyStr); + + static nsresult GenerateRandomizationKeyFromHash( + const nsACString& aRandomizationKeyStr, uint32_t aContentHash, + nsACString& aHex); + private: nsresult Init();