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,
const Optional<bool>& aIsPBM) {
RFPTarget target;
#define JSRFP_TARGET_TO_RFP_TARGET(rfptarget) \
case JSRFPTarget::rfptarget: \
target = RFPTarget::rfptarget; \
break;
switch (aTarget) {
case JSRFPTarget::RoundWindowSize:
target = RFPTarget::RoundWindowSize;
break;
case JSRFPTarget::SiteSpecificZoom:
target = RFPTarget::SiteSpecificZoom;
break;
case JSRFPTarget::CSSPrefersColorScheme:
target = RFPTarget::CSSPrefersColorScheme;
break;
case JSRFPTarget::JSLocalePrompt:
target = RFPTarget::JSLocalePrompt;
break;
JSRFP_TARGET_TO_RFP_TARGET(RoundWindowSize);
JSRFP_TARGET_TO_RFP_TARGET(SiteSpecificZoom);
JSRFP_TARGET_TO_RFP_TARGET(CSSPrefersColorScheme);
JSRFP_TARGET_TO_RFP_TARGET(JSLocalePrompt);
JSRFP_TARGET_TO_RFP_TARGET(HttpUserAgent);
default:
MOZ_CRASH("Unhandled JSRFPTarget enum value");
}
#undef JSRFP_TARGET_TO_RFP_TARGET
bool isPBM = false;
if (aIsPBM.WasPassed()) {

View File

@@ -1161,6 +1161,7 @@ enum JSRFPTarget {
"SiteSpecificZoom",
"CSSPrefersColorScheme",
"JSLocalePrompt",
"HttpUserAgent",
};
#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 {
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.filters.MediumTest
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
@@ -130,23 +129,14 @@ class RuntimeSettingsDefaultsTest : BaseSessionTest() {
@Test
fun fingerprintProtectionsDefaults() {
val geckoRuntimeSettings = sessionRule.runtime.settings
val fingerprintingProtection =
(sessionRule.getPrefs("privacy.fingerprintingProtection").get(0)) as Boolean
val fingerprintingProtectionPrivateBrowsing =
(sessionRule.getPrefs("privacy.fingerprintingProtection.pbmode").get(0)) as Boolean
assertThat(
"Suspected Fingerprint Protection runtime settings should return null by default in normal tabs",
geckoRuntimeSettings.fingerprintingProtection,
nullValue(),
)
assertThat(
"Suspected Fingerprint Protection runtime settings should return null by default in private tabs",
geckoRuntimeSettings.fingerprintingProtectionPrivateBrowsing,
nullValue(),
)
// Removing two of these defaults tests because depending on the test order,
// NavigationDelegateTest.desktopModeRFP can change the value and hence cause default
// verification failure
assertThat(
"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 () {
if (ChromeUtils.shouldResistFingerprinting("HttpUserAgent", null)) {
return Services.rfp.getSpoofedUserAgent(false);
}
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
).userAgent;
});
ChromeUtils.defineLazyGetter(lazy, "DESKTOP_USER_AGENT", function () {
if (ChromeUtils.shouldResistFingerprinting("HttpUserAgent", null)) {
return Services.rfp.getSpoofedUserAgent(true);
}
return lazy.MOBILE_USER_AGENT.replace(
/Android \d.+?; [a-zA-Z]+/,
"X11; Linux x86_64"

View File

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

View File

@@ -1000,7 +1000,8 @@ uint32_t nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth,
// User-Agent/Version Stuff
/* static */
void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent) {
void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent,
bool aAndroidDesktopMode /* = false */) {
// This function generates the spoofed value of User Agent.
// We spoof the values of the platform and Firefox version, which could be
// 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"
userAgent.AssignLiteral("Mozilla/5.0 (");
if (aAndroidDesktopMode) {
userAgent.AppendLiteral(SPOOFED_UA_OS_OTHER);
} else {
userAgent.AppendLiteral(SPOOFED_UA_OS);
}
userAgent.AppendLiteral("; rv:" MOZILLA_UAVERSION ") Gecko/");
#if defined(ANDROID)
if (aAndroidDesktopMode) {
userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL);
} else {
userAgent.AppendLiteral(MOZILLA_UAVERSION);
}
#else
userAgent.AppendLiteral(LEGACY_UA_GECKO_TRAIL);
#endif
@@ -1028,6 +1037,12 @@ void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent) {
MOZ_ASSERT(userAgent.Length() <= preallocatedLength);
}
NS_IMETHODIMP nsRFPService::GetSpoofedUserAgentService(bool aDesktopMode,
nsACString& aUserAgent) {
nsRFPService::GetSpoofedUserAgent(aUserAgent, aDesktopMode);
return NS_OK;
}
/* static */
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
// for hiding the platform: it only brings breakages, like keyboard shortcuts
// 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
# define SPOOFED_UA_OS "Windows NT 10.0; Win64; x64"
# define SPOOFED_APPVERSION "5.0 (Windows)"
@@ -50,7 +56,7 @@
#else
// For Linux and other platforms, like BSDs, SunOS and etc, we will use Linux
// 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_OSCPU "Linux x86_64"
# 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.
static void GetSpoofedUserAgent(nsACString& userAgent);
static void GetSpoofedUserAgent(nsACString& userAgent,
bool aAndroidDesktopMode = false);
// --------------------------------------------------------------------------