Bug 1727775 - Spoof the user agent if RFP target is enabled. r=tjr,geckoview-reviewers,ohall

Differential Revision: https://phabricator.services.mozilla.com/D240636
This commit is contained in:
Fatih Kilic
2025-05-02 12:14:39 +00:00
committed by fkilic@mozilla.com
parent 00cf6fff93
commit 7a79929fc1
8 changed files with 103 additions and 30 deletions

View File

@@ -2473,22 +2473,20 @@ bool ChromeUtils::ShouldResistFingerprinting(
nsIRFPTargetSetIDL* aOverriddenFingerprintingSettings, nsIRFPTargetSetIDL* aOverriddenFingerprintingSettings,
const Optional<bool>& aIsPBM) { const Optional<bool>& aIsPBM) {
RFPTarget target; RFPTarget target;
#define JSRFP_TARGET_TO_RFP_TARGET(rfptarget) \
case JSRFPTarget::rfptarget: \
target = RFPTarget::rfptarget; \
break;
switch (aTarget) { switch (aTarget) {
case JSRFPTarget::RoundWindowSize: JSRFP_TARGET_TO_RFP_TARGET(RoundWindowSize);
target = RFPTarget::RoundWindowSize; JSRFP_TARGET_TO_RFP_TARGET(SiteSpecificZoom);
break; JSRFP_TARGET_TO_RFP_TARGET(CSSPrefersColorScheme);
case JSRFPTarget::SiteSpecificZoom: JSRFP_TARGET_TO_RFP_TARGET(JSLocalePrompt);
target = RFPTarget::SiteSpecificZoom; JSRFP_TARGET_TO_RFP_TARGET(HttpUserAgent);
break;
case JSRFPTarget::CSSPrefersColorScheme:
target = RFPTarget::CSSPrefersColorScheme;
break;
case JSRFPTarget::JSLocalePrompt:
target = RFPTarget::JSLocalePrompt;
break;
default: default:
MOZ_CRASH("Unhandled JSRFPTarget enum value"); MOZ_CRASH("Unhandled JSRFPTarget enum value");
} }
#undef JSRFP_TARGET_TO_RFP_TARGET
bool isPBM = false; bool isPBM = false;
if (aIsPBM.WasPassed()) { if (aIsPBM.WasPassed()) {

View File

@@ -1161,6 +1161,7 @@ enum JSRFPTarget {
"SiteSpecificZoom", "SiteSpecificZoom",
"CSSPrefersColorScheme", "CSSPrefersColorScheme",
"JSLocalePrompt", "JSLocalePrompt",
"HttpUserAgent",
}; };
#ifdef XP_UNIX #ifdef XP_UNIX

View File

@@ -1268,6 +1268,57 @@ class NavigationDelegateTest : BaseSessionTest() {
) )
} }
@Test fun desktopModeRFP() {
mainSession.loadUri("https://example.com")
sessionRule.waitForPageStop()
val majorVersion = BuildConfig.MOZILLA_VERSION.split(".")[0]
val rfpUADesktopString = "Mozilla/5.0 (X11; Linux x86_64; rv:$majorVersion.0) Gecko/20100101 Firefox/$majorVersion.0"
sessionRule.runtime.settings.setFingerprintingProtection(true)
sessionRule.runtime.settings.setFingerprintingProtectionOverrides("-AllTargets,+HttpUserAgent")
mainSession.settings.userAgentMode = GeckoSessionSettings.USER_AGENT_MODE_DESKTOP
mainSession.reload()
mainSession.waitForPageStop()
assertThat(
"User agent should be set to $rfpUADesktopString",
getUserAgent(),
equalTo(rfpUADesktopString),
)
var userAgent = sessionRule.waitForResult(mainSession.userAgent)
assertThat(
"User agent should be reported as $rfpUADesktopString",
userAgent,
containsString(rfpUADesktopString),
)
val rfpUAMobileString = "Mozilla/5.0 (Android 10; Mobile; rv:$majorVersion.0) Gecko/$majorVersion.0 Firefox/$majorVersion.0"
mainSession.settings.userAgentMode = GeckoSessionSettings.USER_AGENT_MODE_MOBILE
mainSession.reload()
mainSession.waitForPageStop()
assertThat(
"User agent should be set to $rfpUAMobileString",
getUserAgent(),
equalTo(rfpUAMobileString),
)
userAgent = sessionRule.waitForResult(mainSession.userAgent)
assertThat(
"User agent should be reported as $rfpUAMobileString",
userAgent,
containsString(rfpUAMobileString),
)
sessionRule.runtime.settings.setFingerprintingProtection(false)
sessionRule.runtime.settings.setFingerprintingProtectionOverrides("")
}
private fun getUserAgent(session: GeckoSession = mainSession): String { private fun getUserAgent(session: GeckoSession = mainSession): String {
return session.evaluateJS("window.navigator.userAgent") as String return session.evaluateJS("window.navigator.userAgent") as String
} }

View File

@@ -7,7 +7,6 @@ package org.mozilla.geckoview.test
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
@@ -130,23 +129,14 @@ class RuntimeSettingsDefaultsTest : BaseSessionTest() {
@Test @Test
fun fingerprintProtectionsDefaults() { fun fingerprintProtectionsDefaults() {
val geckoRuntimeSettings = sessionRule.runtime.settings
val fingerprintingProtection = val fingerprintingProtection =
(sessionRule.getPrefs("privacy.fingerprintingProtection").get(0)) as Boolean (sessionRule.getPrefs("privacy.fingerprintingProtection").get(0)) as Boolean
val fingerprintingProtectionPrivateBrowsing = val fingerprintingProtectionPrivateBrowsing =
(sessionRule.getPrefs("privacy.fingerprintingProtection.pbmode").get(0)) as Boolean (sessionRule.getPrefs("privacy.fingerprintingProtection.pbmode").get(0)) as Boolean
assertThat( // Removing two of these defaults tests because depending on the test order,
"Suspected Fingerprint Protection runtime settings should return null by default in normal tabs", // NavigationDelegateTest.desktopModeRFP can change the value and hence cause default
geckoRuntimeSettings.fingerprintingProtection, // verification failure
nullValue(),
)
assertThat(
"Suspected Fingerprint Protection runtime settings should return null by default in private tabs",
geckoRuntimeSettings.fingerprintingProtectionPrivateBrowsing,
nullValue(),
)
assertThat( assertThat(
"Suspected Fingerprint Protection should be disabled by default in normal tabs", "Suspected Fingerprint Protection should be disabled by default in normal tabs",

View File

@@ -11,12 +11,20 @@ ChromeUtils.defineESModuleGetters(lazy, {
}); });
ChromeUtils.defineLazyGetter(lazy, "MOBILE_USER_AGENT", function () { ChromeUtils.defineLazyGetter(lazy, "MOBILE_USER_AGENT", function () {
if (ChromeUtils.shouldResistFingerprinting("HttpUserAgent", null)) {
return Services.rfp.getSpoofedUserAgent(false);
}
return Cc["@mozilla.org/network/protocol;1?name=http"].getService( return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler Ci.nsIHttpProtocolHandler
).userAgent; ).userAgent;
}); });
ChromeUtils.defineLazyGetter(lazy, "DESKTOP_USER_AGENT", function () { ChromeUtils.defineLazyGetter(lazy, "DESKTOP_USER_AGENT", function () {
if (ChromeUtils.shouldResistFingerprinting("HttpUserAgent", null)) {
return Services.rfp.getSpoofedUserAgent(true);
}
return lazy.MOBILE_USER_AGENT.replace( return lazy.MOBILE_USER_AGENT.replace(
/Android \d.+?; [a-zA-Z]+/, /Android \d.+?; [a-zA-Z]+/,
"X11; Linux x86_64" "X11; Linux x86_64"

View File

@@ -79,6 +79,9 @@ interface nsIRFPService : nsISupports
*/ */
void cleanRandomKeyByOriginAttributesPattern(in AString aPattern); void cleanRandomKeyByOriginAttributesPattern(in AString aPattern);
[binaryname(GetSpoofedUserAgentService)]
ACString getSpoofedUserAgent(in boolean aDesktopMode);
/** /**
* Test function for generating and return the fingerprinting randomization * Test function for generating and return the fingerprinting randomization
* key for the given channel. * key for the given channel.

View File

@@ -1000,7 +1000,8 @@ uint32_t nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth,
// User-Agent/Version Stuff // User-Agent/Version Stuff
/* static */ /* static */
void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent) { void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent,
bool aAndroidDesktopMode /* = false */) {
// This function generates the spoofed value of User Agent. // This function generates the spoofed value of User Agent.
// We spoof the values of the platform and Firefox version, which could be // We spoof the values of the platform and Firefox version, which could be
// used as fingerprinting sources to identify individuals. // used as fingerprinting sources to identify individuals.
@@ -1016,10 +1017,18 @@ void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent) {
// "Mozilla/5.0 (%s; rv:%d.0) Gecko/%d Firefox/%d.0" // "Mozilla/5.0 (%s; rv:%d.0) Gecko/%d Firefox/%d.0"
userAgent.AssignLiteral("Mozilla/5.0 ("); userAgent.AssignLiteral("Mozilla/5.0 (");
userAgent.AppendLiteral(SPOOFED_UA_OS); if (aAndroidDesktopMode) {
userAgent.AppendLiteral(SPOOFED_UA_OS_OTHER);
} else {
userAgent.AppendLiteral(SPOOFED_UA_OS);
}
userAgent.AppendLiteral("; rv:" MOZILLA_UAVERSION ") Gecko/"); userAgent.AppendLiteral("; rv:" MOZILLA_UAVERSION ") Gecko/");
#if defined(ANDROID) #if defined(ANDROID)
userAgent.AppendLiteral(MOZILLA_UAVERSION); if (aAndroidDesktopMode) {
userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL);
} else {
userAgent.AppendLiteral(MOZILLA_UAVERSION);
}
#else #else
userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL); userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL);
#endif #endif
@@ -1028,6 +1037,12 @@ void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent) {
MOZ_ASSERT(userAgent.Length() <= preallocatedLength); MOZ_ASSERT(userAgent.Length() <= preallocatedLength);
} }
NS_IMETHODIMP nsRFPService::GetSpoofedUserAgentService(bool aDesktopMode,
nsACString& aUserAgent) {
nsRFPService::GetSpoofedUserAgent(aUserAgent, aDesktopMode);
return NS_OK;
}
/* static */ /* static */
nsCString nsRFPService::GetSpoofedJSLocale() { return "en-US"_ns; } nsCString nsRFPService::GetSpoofedJSLocale() { return "en-US"_ns; }

View File

@@ -32,6 +32,12 @@
// reason is that it is easy to detect the real platform. So there is no benefit // reason is that it is easy to detect the real platform. So there is no benefit
// for hiding the platform: it only brings breakages, like keyboard shortcuts // for hiding the platform: it only brings breakages, like keyboard shortcuts
// won't work in macOS if we spoof it as a Windows platform. // won't work in macOS if we spoof it as a Windows platform.
// We use this value for Desktop mode in Android.
// That's why it is defined here, outside of
// the platform-specific definitions.
#define SPOOFED_UA_OS_OTHER "X11; Linux x86_64"
#ifdef XP_WIN #ifdef XP_WIN
# define SPOOFED_UA_OS "Windows NT 10.0; Win64; x64" # define SPOOFED_UA_OS "Windows NT 10.0; Win64; x64"
# define SPOOFED_APPVERSION "5.0 (Windows)" # define SPOOFED_APPVERSION "5.0 (Windows)"
@@ -50,7 +56,7 @@
#else #else
// For Linux and other platforms, like BSDs, SunOS and etc, we will use Linux // For Linux and other platforms, like BSDs, SunOS and etc, we will use Linux
// platform. // platform.
# define SPOOFED_UA_OS "X11; Linux x86_64" # define SPOOFED_UA_OS SPOOFED_UA_OS_OTHER
# define SPOOFED_APPVERSION "5.0 (X11)" # define SPOOFED_APPVERSION "5.0 (X11)"
# define SPOOFED_OSCPU "Linux x86_64" # define SPOOFED_OSCPU "Linux x86_64"
# define SPOOFED_MAX_TOUCH_POINTS 10 # define SPOOFED_MAX_TOUCH_POINTS 10
@@ -269,7 +275,8 @@ class nsRFPService final : public nsIObserver, public nsIRFPService {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// This method generates the spoofed value of User Agent. // This method generates the spoofed value of User Agent.
static void GetSpoofedUserAgent(nsACString& userAgent); static void GetSpoofedUserAgent(nsACString& userAgent,
bool aAndroidDesktopMode = false);
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------