Files
tubestation/toolkit/components/resistfingerprinting/nsRFPService.cpp
Chris Peterson ce551e51b9 Bug 870698 - Part 4: Replace Equals("") with EqualsLiteral(""). r=erahm
MozReview-Commit-ID: G1GhyvD29WK
2017-09-06 01:13:45 -07:00

376 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "nsRFPService.h"
#include <algorithm>
#include <time.h>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsCoord.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "nsPrintfCString.h"
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsJSUtils.h"
#include "prenv.h"
#include "js/Date.h"
using namespace mozilla;
using namespace std;
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
#define RFP_SPOOFED_DROPPED_RATIO_PREF "privacy.resistFingerprinting.video_dropped_ratio"
#define RFP_TARGET_VIDEO_RES_PREF "privacy.resistFingerprinting.target_video_res"
#define RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT 30
#define RFP_SPOOFED_DROPPED_RATIO_DEFAULT 5
#define RFP_TARGET_VIDEO_RES_DEFAULT 480
#define PROFILE_INITIALIZED_TOPIC "profile-initial-state"
NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
static StaticRefPtr<nsRFPService> sRFPService;
static bool sInitialized = false;
Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
static uint32_t kResolutionUSec = 100000;
static uint32_t sVideoFramesPerSec;
static uint32_t sVideoDroppedRatio;
static uint32_t sTargetVideoRes;
/* static */
nsRFPService*
nsRFPService::GetOrCreate()
{
if (!sInitialized) {
sRFPService = new nsRFPService();
nsresult rv = sRFPService->Init();
if (NS_FAILED(rv)) {
sRFPService = nullptr;
return nullptr;
}
ClearOnShutdown(&sRFPService);
sInitialized = true;
}
return sRFPService;
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsMSecs(double aTime)
{
if (!IsResistFingerprintingEnabled()) {
return aTime;
}
const double resolutionMSec = kResolutionUSec / 1000.0;
return floor(aTime / resolutionMSec) * resolutionMSec;
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsUSecs(double aTime)
{
if (!IsResistFingerprintingEnabled()) {
return aTime;
}
return floor(aTime / kResolutionUSec) * kResolutionUSec;
}
/* static */
uint32_t
nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality)
{
return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsSecs(double aTime)
{
if (!IsResistFingerprintingEnabled()) {
return aTime;
}
if (kResolutionUSec < 1000000) {
// The resolution is smaller than one sec. Use the reciprocal to avoid
// floating point error.
const double resolutionSecReciprocal = 1000000.0 / kResolutionUSec;
return floor(aTime * resolutionSecReciprocal) / resolutionSecReciprocal;
}
const double resolutionSec = kResolutionUSec / 1000000.0;
return floor(aTime / resolutionSec) * resolutionSec;
}
/* static */
uint32_t
nsRFPService::GetSpoofedTotalFrames(double aTime)
{
double time = ReduceTimePrecisionAsSecs(aTime);
return NSToIntFloor(time * sVideoFramesPerSec);
}
/* static */
uint32_t
nsRFPService::GetSpoofedDroppedFrames(double aTime, uint32_t aWidth, uint32_t aHeight)
{
uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
// The video resolution is less than or equal to the target resolution, we
// report a zero dropped rate for this case.
if (targetRes >= aWidth * aHeight) {
return 0;
}
double time = ReduceTimePrecisionAsSecs(aTime);
// Bound the dropped ratio from 0 to 100.
uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
return NSToIntFloor(time * sVideoFramesPerSec * (boundedDroppedRatio / 100.0));
}
/* static */
uint32_t
nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth, uint32_t aHeight)
{
uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
// The target resolution is greater than the current resolution. For this case,
// there will be no dropped frames, so we report total frames directly.
if (targetRes >= aWidth * aHeight) {
return GetSpoofedTotalFrames(aTime);
}
double time = ReduceTimePrecisionAsSecs(aTime);
// Bound the dropped ratio from 0 to 100.
uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
return NSToIntFloor(time * sVideoFramesPerSec * ((100 - boundedDroppedRatio) / 100.0));
}
/* static */
nsresult
nsRFPService::GetSpoofedUserAgent(nsACString &userAgent)
{
// 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.
// Reference of the format of User Agent:
// https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
nsresult rv;
nsCOMPtr<nsIXULAppInfo> appInfo =
do_GetService("@mozilla.org/xre/app-info;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString appVersion;
rv = appInfo->GetVersion(appVersion);
NS_ENSURE_SUCCESS(rv, rv);
// The browser version will be spoofed as the last ESR version.
// By doing so, the anonymity group will cover more versions instead of one
// version.
uint32_t firefoxVersion = appVersion.ToInteger(&rv);
NS_ENSURE_SUCCESS(rv, rv);
// Starting from Firefox 10, Firefox ESR was released once every seven
// Firefox releases, e.g. Firefox 10, 17, 24, 31, and so on.
// We infer the last and closest ESR version based on this rule.
nsCOMPtr<nsIXULRuntime> runtime =
do_GetService("@mozilla.org/xre/runtime;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString updateChannel;
rv = runtime->GetDefaultUpdateChannel(updateChannel);
NS_ENSURE_SUCCESS(rv, rv);
// If we are running in Firefox ESR, determine whether the formula of ESR
// version has changed. Once changed, we must update the formula in this
// function.
if (updateChannel.EqualsLiteral("esr")) {
MOZ_ASSERT(((firefoxVersion % 7) == 3),
"Please udpate ESR version formula in nsRFPService.cpp");
}
uint32_t spoofedVersion = firefoxVersion - ((firefoxVersion - 3) % 7);
userAgent.Assign(nsPrintfCString(
"Mozilla/5.0 (%s; rv:%d.0) Gecko/%s Firefox/%d.0",
SPOOFED_OSCPU, spoofedVersion, LEGACY_BUILD_ID, spoofedVersion));
return rv;
}
nsresult
nsRFPService::Init()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
NS_ENSURE_SUCCESS(rv, rv);
#if defined(XP_WIN)
rv = obs->AddObserver(this, PROFILE_INITIALIZED_TOPIC, false);
NS_ENSURE_SUCCESS(rv, rv);
#endif
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
NS_ENSURE_TRUE(prefs, NS_ERROR_NOT_AVAILABLE);
rv = prefs->AddObserver(RESIST_FINGERPRINTING_PREF, this, false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddUintVarCache(&sVideoFramesPerSec,
RFP_SPOOFED_FRAMES_PER_SEC_PREF,
RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT);
Preferences::AddUintVarCache(&sVideoDroppedRatio,
RFP_SPOOFED_DROPPED_RATIO_PREF,
RFP_SPOOFED_DROPPED_RATIO_DEFAULT);
Preferences::AddUintVarCache(&sTargetVideoRes,
RFP_TARGET_VIDEO_RES_PREF,
RFP_TARGET_VIDEO_RES_DEFAULT);
// We backup the original TZ value here.
const char* tzValue = PR_GetEnv("TZ");
if (tzValue) {
mInitialTZValue = nsCString(tzValue);
}
// Call UpdatePref() here to cache the value of 'privacy.resistFingerprinting'
// and set the timezone.
UpdatePref();
return rv;
}
void
nsRFPService::UpdatePref()
{
MOZ_ASSERT(NS_IsMainThread());
sPrivacyResistFingerprinting = Preferences::GetBool(RESIST_FINGERPRINTING_PREF);
if (sPrivacyResistFingerprinting) {
PR_SetEnv("TZ=UTC");
JS::SetTimeResolutionUsec(kResolutionUSec);
} else if (sInitialized) {
JS::SetTimeResolutionUsec(0);
// We will not touch the TZ value if 'privacy.resistFingerprinting' is false during
// the time of initialization.
if (!mInitialTZValue.IsEmpty()) {
nsAutoCString tzValue = NS_LITERAL_CSTRING("TZ=") + mInitialTZValue;
static char* tz = nullptr;
// If the tz has been set before, we free it first since it will be allocated
// a new value later.
if (tz) {
free(tz);
}
// PR_SetEnv() needs the input string been leaked intentionally, so
// we copy it here.
tz = ToNewCString(tzValue);
if (tz) {
PR_SetEnv(tz);
}
} else {
#if defined(XP_WIN)
// For Windows, we reset the TZ to an empty string. This will make Windows to use
// its system timezone.
PR_SetEnv("TZ=");
#else
// For POSIX like system, we reset the TZ to the /etc/localtime, which is the
// system timezone.
PR_SetEnv("TZ=:/etc/localtime");
#endif
}
}
nsJSUtils::ResetTimeZone();
}
void
nsRFPService::StartShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->RemoveObserver(RESIST_FINGERPRINTING_PREF, this);
}
}
}
NS_IMETHODIMP
nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
const char16_t* aMessage)
{
if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
NS_ConvertUTF16toUTF8 pref(aMessage);
if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {
UpdatePref();
#if defined(XP_WIN)
if (!XRE_IsE10sParentProcess()) {
// Windows does not follow POSIX. Updates to the TZ environment variable
// are not reflected immediately on that platform as they are on UNIX
// systems without this call.
_tzset();
}
#endif
}
}
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
StartShutdown();
}
#if defined(XP_WIN)
else if (!strcmp(PROFILE_INITIALIZED_TOPIC, aTopic)) {
// If we're e10s, then we don't need to run this, since the child process will
// simply inherit the environment variable from the parent process, in which
// case it's unnecessary to call _tzset().
if (XRE_IsParentProcess() && !XRE_IsE10sParentProcess()) {
// Windows does not follow POSIX. Updates to the TZ environment variable
// are not reflected immediately on that platform as they are on UNIX
// systems without this call.
_tzset();
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
nsresult rv = obs->RemoveObserver(this, PROFILE_INITIALIZED_TOPIC);
NS_ENSURE_SUCCESS(rv, rv);
}
#endif
return NS_OK;
}