diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/components/fuzzyfox | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/fuzzyfox')
-rw-r--r-- | toolkit/components/fuzzyfox/Fuzzyfox.cpp | 382 | ||||
-rw-r--r-- | toolkit/components/fuzzyfox/Fuzzyfox.h | 123 | ||||
-rw-r--r-- | toolkit/components/fuzzyfox/moz.build | 13 |
3 files changed, 518 insertions, 0 deletions
diff --git a/toolkit/components/fuzzyfox/Fuzzyfox.cpp b/toolkit/components/fuzzyfox/Fuzzyfox.cpp new file mode 100644 index 0000000000..ff58c4950c --- /dev/null +++ b/toolkit/components/fuzzyfox/Fuzzyfox.cpp @@ -0,0 +1,382 @@ +/* -*- 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 "Fuzzyfox.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsIPrefBranch.h" +#include "nsServiceManagerUtils.h" +#include "prrng.h" +#include "prtime.h" + +// For usleep/Sleep & QueryPerformanceFrequency +#ifdef XP_WIN +# include <windows.h> +#else +# include <unistd.h> +#endif + +using namespace mozilla; + +static LazyLogModule sFuzzyfoxLog("Fuzzyfox"); + +#define US_TO_NS(x) ((x)*1000) +#define NS_TO_US(x) ((x) / 1000) + +#ifdef LOG +# undef LOG +#endif + +#define LOG(level, args) MOZ_LOG(sFuzzyfoxLog, mozilla::LogLevel::level, args) + +#define FUZZYFOX_ENABLED_PREF "privacy.fuzzyfox.enabled" +#define FUZZYFOX_ENABLED_PREF_DEFAULT false +#define FUZZYFOX_CLOCKGRAIN_PREF "privacy.fuzzyfox.clockgrainus" + +static bool sFuzzyfoxInitializing; + +NS_IMPL_ISUPPORTS_INHERITED(Fuzzyfox, Runnable, nsIObserver) + +/* static */ +void Fuzzyfox::Start() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<Fuzzyfox> r = new Fuzzyfox(); + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); +} + +Fuzzyfox::Fuzzyfox() + : Runnable("Fuzzyfox"), + mSanityCheck(false), + mStartTime(0), + mDuration(PickDuration()), + mTickType(eUptick) { + MOZ_ASSERT(NS_IsMainThread()); + + // [[ I originally ran this after observing profile-after-change, but + // it turned out that this contructor was getting called _after_ that + // event had already fired. ]] + bool fuzzyfoxEnabled = Preferences::GetBool(FUZZYFOX_ENABLED_PREF, + FUZZYFOX_ENABLED_PREF_DEFAULT); + + LOG(Info, ("PT(%p) Created Fuzzyfox, FuzzyFox is now %s \n", this, + (fuzzyfoxEnabled ? "initializing" : "disabled"))); + + sFuzzyfoxInitializing = fuzzyfoxEnabled; + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) { + // Sometimes the call to the pref service fails. If that happens, we + // don't add the observers. The user will have to restart to have + // preference changes take effect. + return; + } + + prefs->AddObserver(FUZZYFOX_ENABLED_PREF, this, false); + prefs->AddObserver(FUZZYFOX_CLOCKGRAIN_PREF, this, false); +} + +Fuzzyfox::~Fuzzyfox() = default; + +/* + * Fuzzyfox::Run is the core of FuzzyFox. Every so often we pop into this + * method, and pick a new point in the future to hold time constant until. If we + * have not reached the _previous_ point in time we had picked, we sleep until + * we do so. Then we round the current time downwards to a configurable grain + * value, fix it in place so time does not advance, and let execution continue. + */ +NS_IMETHODIMP +Fuzzyfox::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(FUZZYFOX_ENABLED_PREF)) { + bool fuzzyfoxEnabled = Preferences::GetBool( + FUZZYFOX_ENABLED_PREF, FUZZYFOX_ENABLED_PREF_DEFAULT); + + LOG(Info, ("PT(%p) Observed a pref change, FuzzyFox is now %s \n", this, + (fuzzyfoxEnabled ? "initializing" : "disabled"))); + + sFuzzyfoxInitializing = fuzzyfoxEnabled; + + if (sFuzzyfoxInitializing) { + // Queue a runnable + nsCOMPtr<nsIRunnable> r = this; + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); + } else { + mStartTime = 0; + mTickType = eUptick; + mSanityCheck = false; + TimeStamp::SetFuzzyfoxEnabled(false); + } + } + } + return NS_OK; +} + +#define DISPATCH_AND_RETURN() \ + nsCOMPtr<nsIRunnable> r = this; \ + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); \ + return NS_OK + +NS_IMETHODIMP +Fuzzyfox::Run() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sFuzzyfoxInitializing && !TimeStamp::GetFuzzyfoxEnabled()) { + LOG(Info, ("[FuzzyfoxEvent] PT(%p) Fuzzyfox is shut down, doing nothing \n", + this)); + return NS_OK; + } + + if (sFuzzyfoxInitializing) { + // This is the first time we are running afer enabling FuzzyFox. We need + // to prevent time from going backwards, so for the first run we round the + // time up to the next grain. + mStartTime = CeilToGrain(ActualTime()); + MOZ_ASSERT(mStartTime != 0); + TimeStamp newTimeStamp = CeilToGrain(TimeStamp::NowUnfuzzed()); + Fuzzyfox::UpdateClocks(mStartTime, newTimeStamp); + + mSanityCheck = true; + LOG(Info, ("[FuzzyfoxEvent] PT(%p) Going to start Fuzzyfox, queuing up the " + "job \n", + this)); + + // Now that we've updated the timestamps, we can let everyone know Fuzzyfox + // is ready to use + TimeStamp::SetFuzzyfoxEnabled(true); + sFuzzyfoxInitializing = false; + + DISPATCH_AND_RETURN(); + } + + // We need to check how long its been since we ran + uint64_t endTime = ActualTime(); + + uint64_t remaining = 0; + + // Pick the amount to sleep + if (endTime < mStartTime) { + // This can only happen if we just enabled FuzzyFox, rounded up, and then + // re-ran the runnable before we advanced to the next grain. If that + // happens, then repeat the current time. We use mSanityCheck just to be + // sure (and will eventually remove it.) + MOZ_ASSERT(mSanityCheck); + LOG(Warning, ("[FuzzyfoxEvent] Unusual!! PT(%p) endTime < mStartTime " + "mStartTime %" PRIu64 " endTime %" PRIu64 " \n", + this, mStartTime, endTime)); + + mSanityCheck = true; + DISPATCH_AND_RETURN(); + } + + uint64_t actualRunDuration = endTime - mStartTime; + LOG(Verbose, + ("[FuzzyfoxEvent] PT(%p) mDuration: %" PRIu32 " endTime: %" PRIu64 + " mStartTime: %" PRIu64 " actualRunDuration: %" PRIu64 " \n", + this, mDuration, endTime, mStartTime, actualRunDuration)); + if (actualRunDuration > mDuration) { + // We ran over our budget! + uint64_t over = actualRunDuration - mDuration; + LOG(Debug, ("[FuzzyfoxEvent] PT(%p) Overran budget of %" PRIu32 + " by %" PRIu64 " \n", + this, mDuration, over)); + + uint64_t nextDuration = PickDuration(); + while (over > nextDuration) { + over -= nextDuration; + nextDuration = PickDuration(); + mTickType = mTickType == eUptick ? eDowntick : eUptick; + } + + remaining = nextDuration - over; + } else { + // Didn't go over budget + remaining = mDuration - actualRunDuration; + LOG(Debug, ("[FuzzyfoxEvent] PT(%p) Finishing budget of %" PRIu32 + " with %" PRIu64 " \n", + this, mDuration, remaining)); + } + mSanityCheck = false; + + // Sleep for now +#ifdef XP_WIN + Sleep(remaining); +#else + usleep(remaining); +#endif + + /* + * Update clocks (and fire pending events etc) + * + * Note: Anytime we round the current time to the grain, and then round the + * 'real' time to the grain, we are introducing the risk that we split the + * grain. That is, the time advances enough after the first rounding that the + * second rounding causes us to move to a different grain. + * + * In theory, such an occurance breaks the security of FuzzyFox, and if an + * attacker can influence the event to occur reliably, and then measure + * against it they can attack FuzzyFox. But such an attack is so difficult + * that it will never be acheived until you read this comment in a future + * Academic Publication that demonstrates it. And at that point the paper + * would surely never be accepted into any _respectable_ journal unless the + * authors had also presented a solution for the issue that was usable and + * incorporated into Firefox! + */ + uint64_t newTime = FloorToGrain(ActualTime()); + TimeStamp newTimeStamp = FloorToGrain(TimeStamp::NowUnfuzzed()); + UpdateClocks(newTime, newTimeStamp); + + // Reset values + mTickType = mTickType == eUptick ? eDowntick : eUptick; + mStartTime = ActualTime(); + mDuration = PickDuration(); + + LOG(Verbose, ("[FuzzyfoxEvent] PT(%p) For next time mDuration: %" PRIu32 + " mStartTime: %" PRIu64 " \n", + this, mDuration, mStartTime)); + + DISPATCH_AND_RETURN(); +} + +/* + * ActualTime returns the unfuzzed/unrounded time in microseconds since the + * epoch + */ +uint64_t Fuzzyfox::ActualTime() { return PR_Now(); } + +/* + * Calculate a duration we will wait until we allow time to advance again + */ +uint64_t Fuzzyfox::PickDuration() { + // TODO: Bug 1484298 - use a real RNG + long int rval = rand(); + + // Avoid divide by zero errors and overflow errors + uint32_t duration = + std::max((uint32_t)1, StaticPrefs::privacy_fuzzyfox_clockgrainus()); + duration = duration >= (UINT32_MAX / 2) ? (UINT32_MAX / 2) : duration; + + // We want uniform distribution from 1->duration*2 + // so that the mean is duration + return 1 + (rval % (duration * 2)); +} + +/* + * Update the TimeStamp's class value for the current (constant) time and + * dispatch the new (constant) timestamp so observers can register to receive it + * to update their own time code. + */ +void Fuzzyfox::UpdateClocks(uint64_t aNewTime, TimeStamp aNewTimeStamp) { +// newTime is the new canonical time for this scope! +#ifndef XP_WIN + LOG(Debug, ("[Time] New time is %" PRIu64 " (compare to %" PRIu64 + ") and timestamp is %" PRIu64 " (compare to %" PRIu64 ")\n", + aNewTime, ActualTime(), aNewTimeStamp.mValue.mTimeStamp, + TimeStamp::NowUnfuzzed().mValue.mTimeStamp)); +#else + LOG(Debug, ("[Time] New time is %" PRIu64 " (compare to %" PRIu64 ") \n", + aNewTime, ActualTime())); +#endif + + // Fire notifications + if (MOZ_UNLIKELY(!mObs)) { + mObs = services::GetObserverService(); + if (NS_WARN_IF(!mObs)) { + return; + } + } + + // Event firings on occur on downticks and have no data + if (mTickType == eDowntick) { + mObs->NotifyObservers(nullptr, FUZZYFOX_FIREOUTBOUND_OBSERVER_TOPIC, + nullptr); + } + + if (!mTimeUpdateWrapper) { + mTimeUpdateWrapper = do_CreateInstance(NS_SUPPORTS_PRINT64_CONTRACTID); + if (NS_WARN_IF(!mTimeUpdateWrapper)) { + return; + } + } + + mTimeUpdateWrapper->SetData(aNewTime); + + // Clocks get the new official (frozen) time. This happens on all ticks + mObs->NotifyObservers(mTimeUpdateWrapper, FUZZYFOX_UPDATECLOCK_OBSERVER_TOPIC, + nullptr); + + // Update the timestamp's canonicaltimes + TimeStamp::UpdateFuzzyTime(aNewTime); + TimeStamp::UpdateFuzzyTimeStamp(aNewTimeStamp); +} + +/* + * FloorToGrain accepts a timestamp in microsecond precision + * and returns it in microseconds, rounded down to the nearest + * ClockGrain value. + */ +uint64_t Fuzzyfox::FloorToGrain(uint64_t aValue) { + return aValue - (aValue % StaticPrefs::privacy_fuzzyfox_clockgrainus()); +} + +/* + * FloorToGrain accepts a timestamp and returns it, rounded down + * to the nearest ClockGrain value. + */ +TimeStamp Fuzzyfox::FloorToGrain(TimeStamp aValue) { +#ifdef XP_WIN + // grain is in us + uint64_t grain = StaticPrefs::privacy_fuzzyfox_clockgrainus(); + // GTC and QPS are stored in |mt| and need to be converted to + uint64_t GTC = mt2ms(aValue.mValue.mGTC) * 1000; + uint64_t QPC = mt2ms(aValue.mValue.mQPC) * 1000; + + return TimeStamp(TimeStampValue(ms2mt((GTC - (GTC % grain)) / 1000), + ms2mt((QPC - (QPC % grain)) / 1000), + aValue.mValue.mHasQPC, true)); +#else + return TimeStamp(TimeStampValue( + true, US_TO_NS(FloorToGrain(NS_TO_US(aValue.mValue.mTimeStamp))))); +#endif +} + +/* + * CeilToGrain accepts a timestamp in microsecond precision + * and returns it in microseconds, rounded up to the nearest + * ClockGrain value. + */ +uint64_t Fuzzyfox::CeilToGrain(uint64_t aValue) { + return (aValue / StaticPrefs::privacy_fuzzyfox_clockgrainus()) * + StaticPrefs::privacy_fuzzyfox_clockgrainus(); +} + +/* + * CeilToGrain accepts a timestamp and returns it, rounded up + * to the nearest ClockGrain value. + */ +TimeStamp Fuzzyfox::CeilToGrain(TimeStamp aValue) { +#ifdef XP_WIN + // grain is in us + uint64_t grain = StaticPrefs::privacy_fuzzyfox_clockgrainus(); + // GTC and QPS are stored in |mt| and need to be converted + uint64_t GTC = mt2ms(aValue.mValue.mGTC) * 1000; + uint64_t QPC = mt2ms(aValue.mValue.mQPC) * 1000; + + return TimeStamp(TimeStampValue(ms2mt(((GTC / grain) * grain) / 1000), + ms2mt(((QPC / grain) * grain) / 1000), + aValue.mValue.mHasQPC, true)); +#else + return TimeStamp(TimeStampValue( + true, US_TO_NS(CeilToGrain(NS_TO_US(aValue.mValue.mTimeStamp))))); +#endif +} diff --git a/toolkit/components/fuzzyfox/Fuzzyfox.h b/toolkit/components/fuzzyfox/Fuzzyfox.h new file mode 100644 index 0000000000..9c2919e861 --- /dev/null +++ b/toolkit/components/fuzzyfox/Fuzzyfox.h @@ -0,0 +1,123 @@ +/* -*- 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/. */ + +#ifndef mozilla_Fuzzyfox_h +#define mozilla_Fuzzyfox_h + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsThreadUtils.h" +#include "mozilla/TimeStamp.h" + +/* + * This topic publishes the new canonical time according to Fuzzyfox, + * in microseconds since the epoch. If code needs to know the current time, + * it should listen for this topic and keep track of the 'current' time, + * so as to respect Fuzzyfox and be in sync with the rest of the browser's + * timekeeping. + */ +#define FUZZYFOX_UPDATECLOCK_OBSERVER_TOPIC "fuzzyfox-update-clocks" + +/* + * For Fuzzyfox's security guarentees to hold, the browser must not execute + * actions while it should be paused. We currently only pause the main thread, + * so actions that occur on other threads should be queued until the browser + * unpaused (and moreso than unpauses: until it reaches a downtick.) + * This topic indicates when any queued outbound events should be delivered. + * TODO: Bug 1484300 and 1484299 would apply this to other communication + * channels + */ +#define FUZZYFOX_FIREOUTBOUND_OBSERVER_TOPIC "fuzzyfox-fire-outbound" + +namespace mozilla { + +/* + * Fuzzyfox is an implementation of the Fermata concept presented in + * Trusted Browsers for Uncertain Times. + * + * Web Browsers expose explicit (performance.now()) and implicit + * (WebVTT, Video Frames) timers that, when combined with algorithmic + * improvements such as edge thresholding, produce extremely high + * resolution clocks. + * + * High Resolution clocks can be used to time network accesses, browser + * cache reads, web page rendering, access to the CPU cache, and other + * operations - and the time these operations take to perform can yield + * detailed information about user information we want to keep private. + * + * Fuzzyfox limits the information disclosure by limiting an attacker's + * ability to create a high resolution clock. It does this by introducing + * a concept called 'fuzzy time' that degrades all clocks (explicit and + * implicit). This is done through a combination of holding time constant + * during program execution and pausing program execution. + * + * @InProceedings{KS16, + * author = {David Kohlbrenner and Hovav Shacham}, + * title = {Trusted Browsers for Uncertain Times}, + * booktitle = {Proceedings of USENIX Security 2016}, + * pages = {463-80}, + * year = 2016, + * editor = {Thorsten Holz and Stefan Savage}, + * month = aug, + * organization = {USENIX} + * } + * https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_kohlbrenner.pdf + * + * Fuzzyfox is an adaptation of + * W.-M. Hu, “Reducing timing channels with fuzzy time,” in + * Proceedings of IEEE Security and Privacy (“Oakland”) + * 1991, T. F. Lunt and J. McLean, Eds. IEEE Computer + * Society, May 1991, pp. 8–20. + */ +class Fuzzyfox final : public Runnable, public nsIObserver { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + + static void Start(); + + NS_IMETHOD + Run() override; + + private: + Fuzzyfox(); + ~Fuzzyfox(); + + uint64_t ActualTime(); + + uint64_t PickDuration(); + + void UpdateClocks(uint64_t aNewTime, TimeStamp aNewTimeStamp); + + uint64_t FloorToGrain(uint64_t aValue); + + TimeStamp FloorToGrain(TimeStamp aValue); + + uint64_t CeilToGrain(uint64_t aValue); + + TimeStamp CeilToGrain(TimeStamp aValue); + + bool mSanityCheck; + uint64_t mStartTime; + uint32_t mDuration; + + enum Tick { + eUptick, + eDowntick, + }; + + Tick mTickType; + + nsCOMPtr<nsIObserverService> mObs = nullptr; + nsCOMPtr<nsISupportsPRInt64> mTimeUpdateWrapper = nullptr; + + static Atomic<bool, Relaxed> sFuzzyfoxEnabledPrefMapped; +}; + +} // namespace mozilla + +#endif /* mozilla_Fuzzyfox_h */ diff --git a/toolkit/components/fuzzyfox/moz.build b/toolkit/components/fuzzyfox/moz.build new file mode 100644 index 0000000000..4ec7b615c4 --- /dev/null +++ b/toolkit/components/fuzzyfox/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "Fuzzyfox.cpp", +] + +EXPORTS.mozilla += ["Fuzzyfox.h"] + +FINAL_LIBRARY = "xul" |