gecko-dev/toolkit/components/resistfingerprinting/nsRFPService.cpp
Jonathan Hao dca1a28633 Bug 1217238 - Reduce time precision when privacy.resistFingerprinting is on. r=mystor
This patch is adapted from Tor bug 1517.

To offer some protection against timing attacks by JS content pages, in this
patch we round the various time-exposing APIs (such as Date and
Event.timeStamps) to the nearest 100 ms when the pref "privacy.resistFingerprinting" is on.

MozReview-Commit-ID: eGucM9nGTn

--HG--
extra : rebase_source : 3ee600b07943f3954e9a2a9561391f2f7821bb86
2017-06-06 11:45:14 +08:00

196 lines
5.0 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 "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsJSUtils.h"
#include "prenv.h"
#include "js/Date.h"
using namespace mozilla;
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
static StaticRefPtr<nsRFPService> sRFPService;
static bool sInitialized = false;
Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
static uint32_t kResolutionUSec = 100000;
/* 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 */
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;
}
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);
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);
// 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;
PR_SetEnv(tzValue.get());
} else {
#if defined(XP_LINUX) || defined (XP_MACOSX)
// For POSIX like system, we reset the TZ to the /etc/localtime, which is the
// system timezone.
PR_SetEnv("TZ=:/etc/localtime");
#else
// For Windows, we reset the TZ to an empty string. This will make Windows to use
// its system timezone.
PR_SetEnv("TZ=");
#endif
}
}
// We don't have to call _tzset() here for Windows since the following
// function nsJSUtils::ResetTimeZone() will call it for us.
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 (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
StartShutdown();
}
return NS_OK;
}