Bug 1369303 - Part 2: Marking the performance timing API always reports 0 and the access of resource timing and user timing becomes NOP when 'privacy.resistFingerprinting' is true. r?baku,arthuredelstein
This patch is going to neutralize the threat of fingerprinting of performance API
by spoofing the value of performance timing into 0, making getEntries* functions
always returns an empty list and making mark() and measure() into NOP methods.
In addition, this patch changes nsContentUtils::ShouldResistFingerprinting() to
allow it can be called in both main thread and worker threads.
MozReview-Commit-ID: C8Jt7KEMe5e
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -2312,27 +2312,37 @@ nsContentUtils::IsCallerChrome()
// If the check failed, look for UniversalXPConnect on the cx compartment.
return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
}
/* static */
bool
nsContentUtils::ShouldResistFingerprinting()
{
- return nsRFPService::IsResistFingerprintingEnabled();
+ if (NS_IsMainThread()) {
+ return nsRFPService::IsResistFingerprintingEnabled();
+ }
+
+ workers::WorkerPrivate* workerPrivate = workers::GetCurrentThreadWorkerPrivate();
+ if (NS_WARN_IF(!workerPrivate)) {
+ return false;
+ }
+ workerPrivate->AssertIsOnWorkerThread();
+
+ return workerPrivate->ResistFingerprintingEnabled();
}
bool
nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
{
if (!aDocShell) {
return false;
}
bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
- return !isChrome && nsRFPService::IsResistFingerprintingEnabled();
+ return !isChrome && ShouldResistFingerprinting();
}
/* static */
void
nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(int32_t aChromeWidth,
int32_t aChromeHeight,
int32_t aScreenWidth,
int32_t aScreenHeight,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -265,16 +265,17 @@ public:
return SubjectPrincipal();
}
static bool LookupBindingMember(JSContext* aCx, nsIContent *aContent,
JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc);
// Check whether we should avoid leaking distinguishing information to JS/CSS.
+ // This function can be called both in the main thread and worker threads.
static bool ShouldResistFingerprinting();
static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
// A helper function to calculate the rounded window size for fingerprinting
// resistance. The rounded size is based on the chrome UI size and available
// screen size. If the inputWidth/Height is greater than the available content
// size, this will report the available content size. Otherwise, it will
// round the size to the nearest upper 200x100.
@@ -2197,17 +2198,17 @@ public:
/*
* Returns true if the browser should attempt to prevent the given caller type
* from collecting distinctive information about the browser that could
* be used to "fingerprint" and track the user across websites.
*/
static bool ResistFingerprinting(mozilla::dom::CallerType aCallerType)
{
return aCallerType != mozilla::dom::CallerType::System &&
- mozilla::nsRFPService::IsResistFingerprintingEnabled();
+ ShouldResistFingerprinting();
}
/**
* Returns true if the browser should show busy cursor when loading page.
*/
static bool UseActivityCursor()
{
return sUseActivityCursor;
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -166,25 +166,37 @@ JSObject*
Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return PerformanceBinding::Wrap(aCx, this, aGivenProto);
}
void
Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
{
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
aRetval = mResourceEntries;
aRetval.AppendElements(mUserEntries);
aRetval.Sort(PerformanceEntryComparator());
}
void
Performance::GetEntriesByType(const nsAString& aEntryType,
nsTArray<RefPtr<PerformanceEntry>>& aRetval)
{
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ aRetval.Clear();
+ return;
+ }
+
if (aEntryType.EqualsLiteral("resource")) {
aRetval = mResourceEntries;
return;
}
aRetval.Clear();
if (aEntryType.EqualsLiteral("mark") ||
@@ -199,16 +211,21 @@ Performance::GetEntriesByType(const nsAS
void
Performance::GetEntriesByName(const nsAString& aName,
const Optional<nsAString>& aEntryType,
nsTArray<RefPtr<PerformanceEntry>>& aRetval)
{
aRetval.Clear();
+ // We return an empty list when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
for (PerformanceEntry* entry : mResourceEntries) {
if (entry->GetName().Equals(aName) &&
(!aEntryType.WasPassed() ||
entry->GetEntryType().Equals(aEntryType.Value()))) {
aRetval.AppendElement(entry);
}
}
@@ -256,16 +273,21 @@ Performance::RoundTime(double aTime) con
return nsRFPService::ReduceTimePrecisionAsMSecs(
floor(aTime / maxResolutionMs) * maxResolutionMs);
}
void
Performance::Mark(const nsAString& aName, ErrorResult& aRv)
{
+ // We add nothing when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
// Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
@@ -320,16 +342,21 @@ Performance::ResolveTimestampFromName(co
}
void
Performance::Measure(const nsAString& aName,
const Optional<nsAString>& aStartMark,
const Optional<nsAString>& aEndMark,
ErrorResult& aRv)
{
+ // We add nothing when 'privacy.resistFingerprinting' is on.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
// Don't add the entry if the buffer is full. XXX should be removed by bug
// 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
DOMHighResTimeStamp startTime;
DOMHighResTimeStamp endTime;
@@ -434,16 +461,22 @@ Performance::SetResourceTimingBufferSize
mResourceTimingBufferSize = aMaxSize;
}
void
Performance::InsertResourceEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(aEntry);
MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
+
+ // We won't add an entry when 'privacy.resistFingerprint' is true.
+ if (nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
return;
}
mResourceEntries.InsertElementSorted(aEntry,
PerformanceEntryComparator());
if (mResourceEntries.Length() == mResourceTimingBufferSize) {
// call onresourcetimingbufferfull
--- a/dom/performance/PerformanceTiming.cpp
+++ b/dom/performance/PerformanceTiming.cpp
@@ -30,17 +30,18 @@ PerformanceTiming::PerformanceTiming(Per
mRedirectCount(0),
mTimingAllowed(true),
mAllRedirectsSameOrigin(true),
mInitialized(!!aChannel),
mReportCrossOriginRedirect(true)
{
MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
mZeroTime = 0;
}
// The aHttpChannel argument is null if this PerformanceTiming object is
// being used for navigation timing (which is only relevant for documents).
// It has a non-null value if this PerformanceTiming object is being used
// for resource timing, which can include document loads, both toplevel and
// in subframes, and resources linked from a document.
@@ -89,17 +90,18 @@ PerformanceTiming::InitializeTimingInfo(
PerformanceTiming::~PerformanceTiming()
{
}
DOMHighResTimeStamp
PerformanceTiming::FetchStartHighRes()
{
if (!mFetchStart) {
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be "
"valid if the performance timing is enabled");
mFetchStart = (!mAsyncOpen.IsNull())
? TimeStampToDOMHighRes(mAsyncOpen)
: 0.0;
}
@@ -146,29 +148,31 @@ bool
PerformanceTiming::TimingAllowed() const
{
return mTimingAllowed;
}
uint16_t
PerformanceTiming::GetRedirectCount() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
if (!mAllRedirectsSameOrigin) {
return 0;
}
return mRedirectCount;
}
bool
PerformanceTiming::ShouldReportCrossOriginRedirect() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return false;
}
// If the redirect count is 0, or if one of the cross-origin
// redirects doesn't have the proper Timing-Allow-Origin header,
// then RedirectStart and RedirectEnd will be set to zero
return (mRedirectCount != 0) && mReportCrossOriginRedirect;
}
@@ -181,17 +185,18 @@ PerformanceTiming::ShouldReportCrossOrig
* It's up to the consumers of this method (PerformanceTiming::RedirectStart()
* and PerformanceResourceTiming::RedirectStart() to make such verifications.
*
* @return a valid timing if the Performance Timing is enabled
*/
DOMHighResTimeStamp
PerformanceTiming::RedirectStartHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
return TimeStampToDOMHighResOrFetchStart(mRedirectStart);
}
DOMTimeMilliSec
PerformanceTiming::RedirectStart()
{
@@ -214,17 +219,18 @@ PerformanceTiming::RedirectStart()
* (PerformanceTiming::RedirectEnd() and
* PerformanceResourceTiming::RedirectEnd() to make such verifications.
*
* @return a valid timing if the Performance Timing is enabled
*/
DOMHighResTimeStamp
PerformanceTiming::RedirectEndHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
return TimeStampToDOMHighResOrFetchStart(mRedirectEnd);
}
DOMTimeMilliSec
PerformanceTiming::RedirectEnd()
{
@@ -237,97 +243,103 @@ PerformanceTiming::RedirectEnd()
return static_cast<int64_t>(RedirectEndHighRes());
}
return 0;
}
DOMHighResTimeStamp
PerformanceTiming::DomainLookupStartHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
return TimeStampToDOMHighResOrFetchStart(mDomainLookupStart);
}
DOMTimeMilliSec
PerformanceTiming::DomainLookupStart()
{
return static_cast<int64_t>(DomainLookupStartHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::DomainLookupEndHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
// Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null
return mDomainLookupEnd.IsNull() ? DomainLookupStartHighRes()
: TimeStampToDOMHighRes(mDomainLookupEnd);
}
DOMTimeMilliSec
PerformanceTiming::DomainLookupEnd()
{
return static_cast<int64_t>(DomainLookupEndHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::ConnectStartHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
return mConnectStart.IsNull() ? DomainLookupEndHighRes()
: TimeStampToDOMHighRes(mConnectStart);
}
DOMTimeMilliSec
PerformanceTiming::ConnectStart()
{
return static_cast<int64_t>(ConnectStartHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::ConnectEndHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
// Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
return mConnectEnd.IsNull() ? ConnectStartHighRes()
: TimeStampToDOMHighRes(mConnectEnd);
}
DOMTimeMilliSec
PerformanceTiming::ConnectEnd()
{
return static_cast<int64_t>(ConnectEndHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::RequestStartHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
return TimeStampToDOMHighResOrFetchStart(mRequestStart);
}
DOMTimeMilliSec
PerformanceTiming::RequestStart()
{
return static_cast<int64_t>(RequestStartHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::ResponseStartHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
if (mResponseStart.IsNull() ||
(!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) {
mResponseStart = mCacheReadStart;
}
return TimeStampToDOMHighResOrFetchStart(mResponseStart);
}
@@ -336,17 +348,18 @@ DOMTimeMilliSec
PerformanceTiming::ResponseStart()
{
return static_cast<int64_t>(ResponseStartHighRes());
}
DOMHighResTimeStamp
PerformanceTiming::ResponseEndHighRes()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return mZeroTime;
}
if (mResponseEnd.IsNull() ||
(!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
mResponseEnd = mCacheReadEnd;
}
// Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
return mResponseEnd.IsNull() ? ResponseStartHighRes()
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -111,33 +111,36 @@ public:
}
virtual JSObject* WrapObject(JSContext *cx,
JS::Handle<JSObject*> aGivenProto) override;
// PerformanceNavigation WebIDL methods
DOMTimeMilliSec NavigationStart() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetNavigationStart();
}
DOMTimeMilliSec UnloadEventStart()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetUnloadEventStart();
}
DOMTimeMilliSec UnloadEventEnd()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetUnloadEventEnd();
}
uint16_t GetRedirectCount() const;
// Checks if the resource is either same origin as the page that started
@@ -175,65 +178,72 @@ public:
DOMTimeMilliSec ConnectStart();
DOMTimeMilliSec ConnectEnd();
DOMTimeMilliSec RequestStart();
DOMTimeMilliSec ResponseStart();
DOMTimeMilliSec ResponseEnd();
DOMTimeMilliSec DomLoading()
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetDomLoading();
}
DOMTimeMilliSec DomInteractive() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetDomInteractive();
}
DOMTimeMilliSec DomContentLoadedEventStart() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetDomContentLoadedEventStart();
}
DOMTimeMilliSec DomContentLoadedEventEnd() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetDomContentLoadedEventEnd();
}
DOMTimeMilliSec DomComplete() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetDomComplete();
}
DOMTimeMilliSec LoadEventStart() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetLoadEventStart();
}
DOMTimeMilliSec LoadEventEnd() const
{
- if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+ if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+ nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetLoadEventEnd();
}
private:
~PerformanceTiming();