From 5ef6bee795eea1eef9e6cdf77530b3af293b0fa1 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Thu, 26 Oct 2023 17:49:00 +0000 Subject: [PATCH] Bug 1857488 - Add the RTPCallerType to JS realms. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D190676 --- js/public/Date.h | 18 +++++-- js/public/RealmOptions.h | 47 ++++++++++--------- js/src/jsapi.cpp | 17 +++++++ js/src/jsdate.cpp | 9 +++- js/src/vm/Realm.h | 3 ++ .../resistfingerprinting/nsRFPService.cpp | 5 +- .../resistfingerprinting/nsRFPService.h | 9 ++-- 7 files changed, 75 insertions(+), 33 deletions(-) diff --git a/js/public/Date.h b/js/public/Date.h index dd82415624e1..49cbe99d0e57 100644 --- a/js/public/Date.h +++ b/js/public/Date.h @@ -33,6 +33,7 @@ #include "mozilla/MathAlgorithms.h" // mozilla::Abs #include "js/Conversions.h" // JS::ToInteger +#include "js/RealmOptions.h" // JS::RTPCallerTypeToken #include "js/TypeDecls.h" #include "js/Value.h" // JS::CanonicalizeNaN, JS::DoubleValue, JS::Value @@ -187,15 +188,26 @@ JS_PUBLIC_API double DayFromYear(double year); JS_PUBLIC_API double DayWithinYear(double time, double year); // The callback will be a wrapper function that accepts a double (the time -// to clamp and jitter). Inside the JS Engine, other parameters that may be -// needed are all constant, so they are handled inside the wrapper function -using ReduceMicrosecondTimePrecisionCallback = double (*)(double, JSContext*); +// to clamp and jitter) and a JS::RTPCallerTypeToken (a wrapper for +// mozilla::RTPCallerType) that can be used to decide the proper clamping +// behavior to use. Inside the JS Engine, other parameters that may be needed +// are all constant, so they are handled inside the wrapper function +using ReduceMicrosecondTimePrecisionCallback = + double (*)(double, JS::RTPCallerTypeToken, JSContext*); // Set a callback into the toolkit/components/resistfingerprinting function that // will centralize time resolution and jitter into one place. +// Defining such a callback requires all Realms that are created afterwards +// to have a set JS::RTPCallerTypeToken, via RealmBehaviors or +// JS::SetRealmReduceTimerPrecisionCallerType. JS_PUBLIC_API void SetReduceMicrosecondTimePrecisionCallback( ReduceMicrosecondTimePrecisionCallback callback); +// Get the previously set ReduceMicrosecondTimePrecisionCallback callback or +// nullptr. +JS_PUBLIC_API ReduceMicrosecondTimePrecisionCallback +GetReduceMicrosecondTimePrecisionCallback(); + // Sets the time resolution for fingerprinting protection, and whether jitter // should occur. If resolution is set to zero, then no rounding or jitter will // occur. This is used if the callback above is not specified. diff --git a/js/public/RealmOptions.h b/js/public/RealmOptions.h index f7ad00220e68..8813fd19d16c 100644 --- a/js/public/RealmOptions.h +++ b/js/public/RealmOptions.h @@ -14,6 +14,7 @@ #define js_RealmOptions_h #include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" #include "jstypes.h" // JS_PUBLIC_API @@ -320,6 +321,12 @@ class JS_PUBLIC_API RealmCreationOptions { bool alwaysUseFdlibm_ = false; }; +// This is a wrapper for mozilla::RTPCallerType, that can't easily +// be exposed to the JS engine for layering reasons. +struct RTPCallerTypeToken { + uint8_t value; +}; + /** * RealmBehaviors specifies behaviors of a realm that can be changed after the * realm's been created. @@ -328,6 +335,17 @@ class JS_PUBLIC_API RealmBehaviors { public: RealmBehaviors() = default; + // When a JS::ReduceMicrosecondTimePrecisionCallback callback is defined via + // JS::SetReduceMicrosecondTimePrecisionCallback, a JS::RTPCallerTypeToken (a + // wrapper for mozilla::RTPCallerType) needs to be set for every Realm. + mozilla::Maybe reduceTimerPrecisionCallerType() const { + return rtpCallerType; + } + RealmBehaviors& setReduceTimerPrecisionCallerType(RTPCallerTypeToken type) { + rtpCallerType = mozilla::Some(type); + return *this; + } + // For certain globals, we know enough about the code that will run in them // that we can discard script source entirely. bool discardSource() const { return discardSource_; } @@ -342,29 +360,6 @@ class JS_PUBLIC_API RealmBehaviors { return *this; } - class Override { - public: - Override() : mode_(Default) {} - - bool get(bool defaultValue) const { - if (mode_ == Default) { - return defaultValue; - } - return mode_ == ForceTrue; - } - - void set(bool overrideValue) { - mode_ = overrideValue ? ForceTrue : ForceFalse; - } - - void reset() { mode_ = Default; } - - private: - enum Mode { Default, ForceTrue, ForceFalse }; - - Mode mode_; - }; - // A Realm can stop being "live" in all the ways that matter before its global // is actually GCed. Consumers that tear down parts of a Realm or its global // before that point should set isNonLive accordingly. @@ -375,6 +370,7 @@ class JS_PUBLIC_API RealmBehaviors { } private: + mozilla::Maybe rtpCallerType; bool discardSource_ = false; bool clampAndJitterTime_ = true; bool isNonLive_ = false; @@ -423,6 +419,11 @@ extern JS_PUBLIC_API const RealmBehaviors& RealmBehaviorsRef(JSContext* cx); extern JS_PUBLIC_API void SetRealmNonLive(Realm* realm); +// This behaves like RealmBehaviors::setReduceTimerPrecisionCallerType, but +// can be used even after the Realm has already been created. +extern JS_PUBLIC_API void SetRealmReduceTimerPrecisionCallerType( + Realm* realm, RTPCallerTypeToken type); + } // namespace JS #endif // js_RealmOptions_h diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 8e712812ad9f..74272566759e 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -46,6 +46,7 @@ #include "js/CompileOptions.h" #include "js/ContextOptions.h" // JS::ContextOptions{,Ref} #include "js/Conversions.h" +#include "js/Date.h" // JS::GetReduceMicrosecondTimePrecisionCallback #include "js/ErrorInterceptor.h" #include "js/ErrorReport.h" // JSErrorBase #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* @@ -1761,6 +1762,11 @@ const JS::RealmBehaviors& JS::RealmBehaviorsRef(JSContext* cx) { void JS::SetRealmNonLive(Realm* realm) { realm->setNonLive(); } +void JS::SetRealmReduceTimerPrecisionCallerType(Realm* realm, + JS::RTPCallerTypeToken type) { + realm->setReduceTimerPrecisionCallerType(type); +} + JS_PUBLIC_API JSObject* JS_NewGlobalObject(JSContext* cx, const JSClass* clasp, JSPrincipals* principals, JS::OnNewGlobalHookOption hookOption, @@ -1817,7 +1823,18 @@ JS_PUBLIC_API void JS_FireOnNewGlobalObject(JSContext* cx, // This infallibility will eat OOM and slow script, but if that happens // we'll likely run up into them again soon in a fallible context. cx->check(global); + Rooted globalObject(cx, &global->as()); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (JS::GetReduceMicrosecondTimePrecisionCallback()) { + MOZ_DIAGNOSTIC_ASSERT(globalObject->realm() + ->behaviors() + .reduceTimerPrecisionCallerType() + .isSome(), + "Trying to create a global without setting an " + "explicit RTPCallerType!"); + } +#endif DebugAPI::onNewGlobalObject(cx, globalObject); cx->runtime()->ensureRealmIsRecordingAllocations(globalObject); } diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 07a9f4f485d1..3a23926ee797 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -498,6 +498,11 @@ JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback( sReduceMicrosecondTimePrecisionCallback = callback; } +JS_PUBLIC_API JS::ReduceMicrosecondTimePrecisionCallback +JS::GetReduceMicrosecondTimePrecisionCallback() { + return sReduceMicrosecondTimePrecisionCallback; +} + JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) { sResolutionUsec = resolution; sJitter = jitter; @@ -1856,7 +1861,9 @@ static ClippedTime NowAsMillis(JSContext* cx) { double now = PRMJ_Now(); bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime(); if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) { - now = sReduceMicrosecondTimePrecisionCallback(now, cx); + now = sReduceMicrosecondTimePrecisionCallback( + now, cx->realm()->behaviors().reduceTimerPrecisionCallerType().value(), + cx); } else if (clampAndJitter && sResolutionUsec) { double clamped = floor(now / sResolutionUsec) * sResolutionUsec; diff --git a/js/src/vm/Realm.h b/js/src/vm/Realm.h index deabe4cb97a0..150780005562 100644 --- a/js/src/vm/Realm.h +++ b/js/src/vm/Realm.h @@ -471,6 +471,9 @@ class JS::Realm : public JS::shadow::Realm { const JS::RealmBehaviors& behaviors() const { return behaviors_; } void setNonLive() { behaviors_.setNonLive(); } + void setReduceTimerPrecisionCallerType(JS::RTPCallerTypeToken type) { + behaviors_.setReduceTimerPrecisionCallerType(type); + } /* Whether to preserve JIT code on non-shrinking GCs. */ bool preserveJitCode() { return creationOptions_.preserveJitCode(); } diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp index 1c90850318a1..f271de2d8546 100644 --- a/toolkit/components/resistfingerprinting/nsRFPService.cpp +++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp @@ -661,13 +661,14 @@ double nsRFPService::ReduceTimePrecisionAsSecsRFPOnly( } /* static */ -double nsRFPService::ReduceTimePrecisionAsUSecsWrapper(double aTime, - JSContext* aCx) { +double nsRFPService::ReduceTimePrecisionAsUSecsWrapper( + double aTime, JS::RTPCallerTypeToken aCallerType, JSContext* aCx) { MOZ_ASSERT(aCx); nsCOMPtr global = xpc::CurrentNativeGlobal(aCx); MOZ_ASSERT(global); RTPCallerType callerType = global->GetRTPCallerType(); + return nsRFPService::ReduceTimePrecisionImpl( aTime, MicroSeconds, TimerResolution(callerType), 0, /* For absolute timestamps (all the JS engine does), supply zero diff --git a/toolkit/components/resistfingerprinting/nsRFPService.h b/toolkit/components/resistfingerprinting/nsRFPService.h index c3c446af040a..fdb2f84a87e4 100644 --- a/toolkit/components/resistfingerprinting/nsRFPService.h +++ b/toolkit/components/resistfingerprinting/nsRFPService.h @@ -13,6 +13,7 @@ #include "mozilla/BasicEvents.h" #include "mozilla/gfx/Types.h" #include "mozilla/TypedEnumBits.h" +#include "js/RealmOptions.h" #include "nsHashtablesFwd.h" #include "nsICookieJarSettings.h" #include "nsIFingerprintingWebCompatService.h" @@ -197,10 +198,6 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static double ReduceTimePrecisionAsSecsRFPOnly(double aTime, int64_t aContextMixin, RTPCallerType aRTPCallerType); - - // Used by the JS Engine, as it doesn't know about the TimerPrecisionType enum - static double ReduceTimePrecisionAsUSecsWrapper(double aTime, JSContext* aCx); - // Public only for testing purposes static double ReduceTimePrecisionImpl(double aTime, TimeScale aTimeScale, double aResolutionUSec, @@ -348,6 +345,10 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { // -------------------------------------------------------------------------- + // Used by the JS Engine + static double ReduceTimePrecisionAsUSecsWrapper( + double aTime, JS::RTPCallerTypeToken aCallerType, JSContext* aCx); + static TimerPrecisionType GetTimerPrecisionType(RTPCallerType aRTPCallerType); static TimerPrecisionType GetTimerPrecisionTypeRFPOnly(