Bug 1217238 - Reduce time precision when privacy.resistFingerprinting is on.
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
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -27,16 +27,17 @@
#include "nsIFrame.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"
#include "nsIScrollableFrame.h"
#include "nsJSEnvironment.h"
#include "nsLayoutUtils.h"
#include "nsPIWindowRoot.h"
+#include "nsRFPService.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
static char *sPopupAllowedEvents;
static bool sReturnHighResTimeStamp = false;
@@ -1089,17 +1090,17 @@ Event::DefaultPrevented(CallerType aCall
// If preventDefault() has been called by content, return true. Otherwise,
// i.e., preventDefault() has been called by chrome, return true only when
// this is called by chrome.
return mEvent->DefaultPreventedByContent() ||
aCallerType == CallerType::System;
}
double
-Event::TimeStamp() const
+Event::TimeStampImpl() const
{
if (!sReturnHighResTimeStamp) {
return static_cast<double>(mEvent->mTime);
}
if (mEvent->mTimeStamp.IsNull()) {
return 0.0;
}
@@ -1124,16 +1125,22 @@ Event::TimeStamp() const
workers::WorkerPrivate* workerPrivate =
workers::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
return workerPrivate->TimeStampToDOMHighRes(mEvent->mTimeStamp);
}
+double
+Event::TimeStamp() const
+{
+ return nsRFPService::ReduceTimePrecisionAsMSecs(TimeStampImpl());
+}
+
bool
Event::GetPreventDefault() const
{
nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner));
if (win) {
if (nsIDocument* doc = win->GetExtantDoc()) {
doc->WarnOnceAbout(nsIDocument::eGetPreventDefault);
}
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -58,16 +58,17 @@ public:
protected:
virtual ~Event();
private:
void ConstructorInit(EventTarget* aOwner,
nsPresContext* aPresContext,
WidgetEvent* aEvent);
+ double TimeStampImpl() const;
public:
static Event* FromSupports(nsISupports* aSupports)
{
nsIDOMEvent* event =
static_cast<nsIDOMEvent*>(aSupports);
#ifdef DEBUG
{
--- a/dom/file/BaseBlobImpl.cpp
+++ b/dom/file/BaseBlobImpl.cpp
@@ -1,15 +1,16 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "mozilla/dom/BaseBlobImpl.h"
+#include "nsRFPService.h"
#include "prtime.h"
namespace mozilla {
namespace dom {
void
BaseBlobImpl::GetName(nsAString& aName) const
{
@@ -58,17 +59,17 @@ BaseBlobImpl::GetType(nsAString& aType)
aType = mContentType;
}
int64_t
BaseBlobImpl::GetLastModified(ErrorResult& aRv)
{
MOZ_ASSERT(mIsFile, "Should only be called on files");
if (IsDateUnknown()) {
- mLastModificationDate = PR_Now();
+ mLastModificationDate = nsRFPService::ReduceTimePrecisionAsUSecs(PR_Now());
}
return mLastModificationDate / PR_USEC_PER_MSEC;
}
void
BaseBlobImpl::SetLastModified(int64_t aLastModified)
{
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -6,16 +6,17 @@
#include "MultipartBlobImpl.h"
#include "jsfriendapi.h"
#include "mozilla/dom/BlobSet.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsDOMClassInfoID.h"
#include "nsIMultiplexInputStream.h"
+#include "nsRFPService.h"
#include "nsStringStream.h"
#include "nsTArray.h"
#include "nsJSUtils.h"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
#include "nsIXPConnect.h"
#include <algorithm>
@@ -264,18 +265,18 @@ MultipartBlobImpl::SetLengthAndModifiedD
mLength = totalLength;
if (mIsFile) {
// We cannot use PR_Now() because bug 493756 and, for this reason:
// var x = new Date(); var f = new File(...);
// x.getTime() < f.dateModified.getTime()
// could fail.
- mLastModificationDate =
- lastModifiedSet ? lastModified * PR_USEC_PER_MSEC : JS_Now();
+ mLastModificationDate = nsRFPService::ReduceTimePrecisionAsUSecs(
+ lastModifiedSet ? lastModified * PR_USEC_PER_MSEC : JS_Now());
}
}
void
MultipartBlobImpl::GetMozFullPathInternal(nsAString& aFilename,
ErrorResult& aRv) const
{
if (!mIsFromNsIFile || mBlobImpls.Length() == 0) {
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -1,15 +1,16 @@
/* -*- 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 "DOMMediaStream.h"
#include "nsContentUtils.h"
+#include "nsRFPService.h"
#include "nsServiceManagerUtils.h"
#include "nsIScriptError.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackEvent.h"
#include "mozilla/dom/LocalMediaStreamBinding.h"
@@ -548,18 +549,18 @@ DOMMediaStream::Constructor(const Global
}
double
DOMMediaStream::CurrentTime()
{
if (!mPlaybackStream) {
return 0.0;
}
- return mPlaybackStream->
- StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime);
+ return nsRFPService::ReduceTimePrecisionAsSecs(mPlaybackStream->
+ StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime));
}
void
DOMMediaStream::GetId(nsAString& aID) const
{
aID = mID;
}
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -55,16 +55,17 @@
#include "MediaStreamAudioDestinationNode.h"
#include "MediaStreamAudioSourceNode.h"
#include "MediaStreamGraph.h"
#include "nsContentUtils.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
+#include "nsRFPService.h"
#include "OscillatorNode.h"
#include "PannerNode.h"
#include "PeriodicWave.h"
#include "ScriptProcessorNode.h"
#include "StereoPannerNode.h"
#include "WaveShaperNode.h"
namespace mozilla {
@@ -628,17 +629,18 @@ AudioContext::DestinationStream() const
}
return nullptr;
}
double
AudioContext::CurrentTime() const
{
MediaStream* stream = Destination()->Stream();
- return stream->StreamTimeToSeconds(stream->GetCurrentTime());
+ return nsRFPService::ReduceTimePrecisionAsSecs(
+ stream->StreamTimeToSeconds(stream->GetCurrentTime()));
}
void AudioContext::DisconnectFromOwner()
{
mIsDisconnecting = true;
Shutdown();
DOMEventTargetHelper::DisconnectFromOwner();
}
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -2,16 +2,17 @@
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "Performance.h"
#include "GeckoProfiler.h"
+#include "nsRFPService.h"
#ifdef MOZ_GECKO_PROFILER
#include "ProfilerMarkerPayload.h"
#endif
#include "PerformanceEntry.h"
#include "PerformanceMainThread.h"
#include "PerformanceMark.h"
#include "PerformanceMeasure.h"
#include "PerformanceObserver.h"
@@ -247,17 +248,18 @@ Performance::ClearResourceTimings()
DOMHighResTimeStamp
Performance::RoundTime(double aTime) const
{
// Round down to the nearest 5us, because if the timer is too accurate people
// can do nasty timing attacks with it. See similar code in the worker
// Performance implementation.
const double maxResolutionMs = 0.005;
- return floor(aTime / maxResolutionMs) * maxResolutionMs;
+ return nsRFPService::ReduceTimePrecisionAsMSecs(
+ floor(aTime / maxResolutionMs) * maxResolutionMs);
}
void
Performance::Mark(const nsAString& aName, ErrorResult& aRv)
{
// Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -168,11 +168,16 @@ DayFromYear(double year);
// Takes an integer number of milliseconds since the epoch and an integer year,
// returns the number of days in that year. If |time| is nonfinite, returns NaN.
// Otherwise |time| *must* correspond to a time within the valid year |year|.
// This should usually be ensured by computing |year| as |JS::DayFromYear(time)|.
JS_PUBLIC_API(double)
DayWithinYear(double time, double year);
+// Sets the time resolution for fingerprinting protection.
+// If it's set to zero, then no rounding will happen.
+JS_PUBLIC_API(void)
+SetTimeResolutionUsec(uint32_t resolution);
+
} // namespace JS
#endif /* js_Date_h */
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -13,16 +13,17 @@
* might have been left to the operator."
*
* Frederick Brooks, 'The Second-System Effect'.
*/
#include "jsdate.h"
#include "mozilla/ArrayUtils.h"
+#include "mozilla/Atomics.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Sprintf.h"
#include <ctype.h>
#include <math.h>
#include <string.h>
#include "jsapi.h"
@@ -43,27 +44,32 @@
#include "vm/String.h"
#include "vm/StringBuffer.h"
#include "vm/Time.h"
#include "jsobjinlines.h"
using namespace js;
+using mozilla::Atomic;
using mozilla::ArrayLength;
using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::NumbersAreIdentical;
+using mozilla::ReleaseAcquire;
using JS::AutoCheckCannotGC;
using JS::ClippedTime;
using JS::GenericNaN;
using JS::TimeClip;
using JS::ToInteger;
+// When this value is non-zero, we'll round the time by this resolution.
+static Atomic<uint32_t, ReleaseAcquire> sResolutionUsec;
+
/*
* The JS 'Date' object is patterned after the Java 'Date' object.
* Here is a script:
*
* today = new Date();
*
* print(today.toLocaleString());
*
@@ -394,16 +400,22 @@ JS::DayFromYear(double year)
}
JS_PUBLIC_API(double)
JS::DayWithinYear(double time, double year)
{
return ::DayWithinYear(time, year);
}
+JS_PUBLIC_API(void)
+JS::SetTimeResolutionUsec(uint32_t resolution)
+{
+ sResolutionUsec = resolution;
+}
+
/*
* Find a year for which any given date will fall on the same weekday.
*
* This function should be used with caution when used other than
* for determining DST; it hasn't been proven not to produce an
* incorrect year for times near year boundaries.
*/
static int
@@ -1245,17 +1257,21 @@ date_parse(JSContext* cx, unsigned argc,
args.rval().set(TimeValue(result));
return true;
}
static ClippedTime
NowAsMillis()
{
- return TimeClip(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
+ double now = PRMJ_Now();
+ if (sResolutionUsec) {
+ now = floor(now / sResolutionUsec) * sResolutionUsec;
+ }
+ return TimeClip(now / PRMJ_USEC_PER_MSEC);
}
bool
js::date_now(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(TimeValue(NowAsMillis()));
return true;
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -8,16 +8,17 @@
#include "mozilla/Attributes.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/EventForwards.h"
#include "AnimationCommon.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/Keyframe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TimeStamp.h"
+#include "nsRFPService.h"
class nsIGlobalObject;
class nsStyleContext;
struct nsStyleDisplay;
struct ServoComputedValues;
namespace mozilla {
namespace css {
@@ -46,17 +47,18 @@ struct AnimationEventInfo {
dom::Animation* aAnimation)
: mElement(aElement)
, mAnimation(aAnimation)
, mEvent(true, aMessage)
, mTimeStamp(aTimeStamp)
{
// XXX Looks like nobody initialize WidgetEvent::time
mEvent.mAnimationName = aAnimationName;
- mEvent.mElapsedTime = aElapsedTime.ToSeconds();
+ mEvent.mElapsedTime =
+ nsRFPService::ReduceTimePrecisionAsSecs(aElapsedTime.ToSeconds());
mEvent.mPseudoElement =
AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType);
}
// InternalAnimationEvent doesn't support copy-construction, so we need
// to ourselves in order to work with nsTArray
AnimationEventInfo(const AnimationEventInfo& aOther)
: mElement(aOther.mElement)
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -16,25 +16,28 @@
#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;
-bool nsRFPService::sPrivacyResistFingerprinting = false;
+Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
+static uint32_t kResolutionUSec = 100000;
/* static */
nsRFPService*
nsRFPService::GetOrCreate()
{
if (!sInitialized) {
sRFPService = new nsRFPService();
nsresult rv = sRFPService->Init();
@@ -46,16 +49,54 @@ nsRFPService::GetOrCreate()
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();
@@ -80,21 +121,24 @@ nsRFPService::Init()
// 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
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -1,16 +1,17 @@
/* -*- 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/. */
#ifndef __nsRFPService_h__
#define __nsRFPService_h__
+#include "mozilla/Atomics.h"
#include "nsIObserver.h"
#include "nsString.h"
namespace mozilla {
class nsRFPService final : public nsIObserver
{
@@ -19,26 +20,31 @@ public:
NS_DECL_NSIOBSERVER
static nsRFPService* GetOrCreate();
static bool IsResistFingerprintingEnabled()
{
return sPrivacyResistFingerprinting;
}
+ // The following Reduce methods can be called off main thread.
+ static double ReduceTimePrecisionAsMSecs(double aTime);
+ static double ReduceTimePrecisionAsUSecs(double aTime);
+ static double ReduceTimePrecisionAsSecs(double aTime);
+
private:
nsresult Init();
nsRFPService() {}
~nsRFPService() {}
void UpdatePref();
void StartShutdown();
- static bool sPrivacyResistFingerprinting;
+ static Atomic<bool, ReleaseAcquire> sPrivacyResistFingerprinting;
nsCString mInitialTZValue;
};
} // mozilla namespace
#endif /* __nsRFPService_h__ */