summaryrefslogtreecommitdiffstats
path: root/toolkit/components/fuzzyfox/Fuzzyfox.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/components/fuzzyfox/Fuzzyfox.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.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/Fuzzyfox.cpp')
-rw-r--r--toolkit/components/fuzzyfox/Fuzzyfox.cpp382
1 files changed, 382 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
+}