Files
sousa-gecko/toolkit/components/content-classifier/ContentClassifierService.h
T
Benjamin VanderSloot b6181f191c Bug 2044696 - Add ContentClassifier feature for harmful-addon - r=timhuang,extension-reviewers,robwu,baku
This ended up being a bit of a mess because the cancellation logic for
this protection is also custom and has a custom telemetry ping.
This means two sources of custom logic for each probe. Luckily it's less
custom than before in spite of this.

Differential Revision: https://phabricator.services.mozilla.com/D307323
2026-07-01 17:07:31 +00:00

363 lines
14 KiB
C++

/* 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_ContentClassifierService_h
#define mozilla_ContentClassifierService_h
#include <cstdint>
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ScopedPrefs.h"
#include "mozilla/Span.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ThreadSafety.h"
#include "mozilla/net/ChannelClassifierUtils.h"
#include "nsIAsyncShutdown.h"
#include "nsIChannel.h"
#include "nsIClassifiedChannel.h"
#include "nsIContentClassifierService.h"
#include "nsIContentClassifierRemoteSettingsClient.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsTArray.h"
#include "nsTHashMap.h"
#include "mozilla/ContentClassifierEngine.h"
class nsISerialEventTarget;
namespace mozilla {
enum class ClassifyMode { Annotate, Cancel };
struct ContentClassifierFeature {
// Feature identifier used by prefs and lookups.
nsLiteralCString mName;
// RemoteSettings record names whose attachments are merged into a
// single ContentClassifierEngine for this feature.
Span<const nsLiteralCString> mListIds;
// nsIClassifiedChannel::ClassificationFlags value set on the channel
// on an annotation match (consumed via UrlClassifierCommon::Annotate-
// Channel -> SetClassificationFlagsHelper).
nsIClassifiedChannel::ClassificationFlags mClassificationFlag;
// nsIWebProgressListener::STATE_LOADED_* value logged in the content
// blocking log on an annotation match. The corresponding STATE_BLOCKED_*
// value for a cancellation match is derived from mBlockingErrorCode by
// UrlClassifierCommon::SetBlockedContent. Set to 0 for features that
// only annotate the channel and never emit a content blocking log
// entry (mirrors UrlClassifierCommon::AnnotateChannelWithoutNotifying
// behavior).
uint32_t mLoadedState;
// nsIWebProgressListener::STATE_REPLACED_* value logged in the content
// blocking log on a blocking match that ends up replaced.
uint32_t mReplacedState;
// nsIWebProgressListener::STATE_ALLOWED_* value logged in the content
// blocking log on a blocking match that ends up allowed.
uint32_t mAllowedState;
// NS_ERROR_*_URI value passed to UrlClassifierCommon::SetBlockedContent
// on a cancellation match. SetBlockedContent uses this to set both the
// load-info blocking reason and (via GetClassifierBlockingEventCode)
// the STATE_BLOCKED_* content blocking log entry. Set to NS_OK for
// features that have no blocking variant in url-classifier; consumers
// must check this before attempting to use the feature in blocking
// mode.
nsresult mBlockingErrorCode;
// True if this feature only contains exception/allowlist rules. Used to
// sanity-check that such features are listed after the features they may
// except in the engines pref.
bool mExceptionOnly;
// The scoped pref that gates this feature's blocking decision. When the
// scoped pref is set to false, blocking is suppressed for the channel's site.
// Only consulted on the blocking path.
Maybe<nsIScopedPrefs::Pref> mReferencedScopedPref;
// Optional predicate evaluated before classification. When non-null, the
// feature's engine is skipped for any request where this returns false.
// Allows features to restrict classification to a request subset without
// needing per-request engine variants.
bool (*mRequestFilter)(const ContentClassifierRequest&);
// Optional callback invoked just before a matched channel is cancelled, after
// SetBlockedContent and before aChannel->Cancel(). Null for most features.
void (*mCancelChannelCallback)(nsIChannel*);
};
enum class InitPhase {
NotInited,
InitSucceeded,
InitFailed,
ShutdownStarted,
ShutdownEnded
};
// Aggregated status across all engines consulted for a single
// classification. Ordering is significant: higher values supersede
// lower ones in ContentClassifierResult::Accumulate, so any Exception
// promotes the aggregate over a Hit, and an Important variant pins the
// status against later non-Important results.
enum class ContentClassifierResultStatus : uint8_t {
Miss = 0,
Hit = 1,
Exception = 2,
ImportantHit = 3,
ImportantException = 4,
};
// Aggregated outcome across the engines consulted for one classification.
// Records every engine result that contributed (a match or an exception),
// so the caller can attribute the channel-side annotation / block to the
// right feature definitions.
class ContentClassifierResult {
public:
using Status = ContentClassifierResultStatus;
ContentClassifierResult() = default;
ContentClassifierResult(ContentClassifierResult&&) = default;
ContentClassifierResult& operator=(ContentClassifierResult&&) = default;
ContentClassifierResult(const ContentClassifierResult&) = delete;
ContentClassifierResult& operator=(const ContentClassifierResult&) = delete;
Status GetStatus() const { return mStatus; }
bool Hit() const {
return mStatus == Status::Hit || mStatus == Status::ImportantHit;
}
bool Exception() const {
return mStatus == Status::Exception ||
mStatus == Status::ImportantException;
}
bool Important() const {
return mStatus == Status::ImportantHit ||
mStatus == Status::ImportantException;
}
const nsTArray<ContentClassifierEngineResult>& EngineResults() const {
return mEngineResults;
}
void Accumulate(ContentClassifierEngineResult aEngineResult);
private:
Status mStatus = Status::Miss;
nsTArray<ContentClassifierEngineResult> mEngineResults;
};
// Snapshot of the four engines prefs (cancel/annotate x normal/PBM)
// captured on the main thread. UpdateFeatures threads this snapshot
// through the main-thread fetch step, the build-thread engine build,
// and the lock-holding install/populate/prune step so every stage in
// the rebuild pipeline sees a consistent view of the pref state, even
// if a later pref change races the rebuild.
struct EnginesPrefsSnapshot {
EnginesPrefsSnapshot() {
AppendFeatureNamesFromPref(
"privacy.trackingprotection.content.protection.engines", mCancel);
AppendFeatureNamesFromPref(
"privacy.trackingprotection.content.protection.engines.pbmode",
mCancelPBM);
AppendFeatureNamesFromPref(
"privacy.trackingprotection.content.annotation.engines", mAnnotate);
AppendFeatureNamesFromPref(
"privacy.trackingprotection.content.annotation.engines.pbmode",
mAnnotatePBM);
}
static void AppendFeatureNamesFromPref(const char* aPref,
nsTArray<nsCString>& aOut) {
nsAutoCString value;
Preferences::GetCString(aPref, value);
for (const auto& part : value.Split(',')) {
nsAutoCString name(part);
name.Trim("\b\t\r\n ");
if (!name.IsEmpty() && !aOut.Contains(name)) {
aOut.AppendElement(name);
}
}
}
nsTArray<nsCString> mCancel;
nsTArray<nsCString> mCancelPBM;
nsTArray<nsCString> mAnnotate;
nsTArray<nsCString> mAnnotatePBM;
};
class ContentClassifierService final : public nsIAsyncShutdownBlocker,
public nsIContentClassifierService {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIASYNCSHUTDOWNBLOCKER
NS_DECL_NSICONTENTCLASSIFIERSERVICE
static already_AddRefed<ContentClassifierService> GetInstance();
static bool IsEnabled();
static bool IsInitialized();
// Returns the static table of content classifier features. The table
// is the single source of truth for how filter list IDs are grouped
// into engines and how matches are reported.
static Span<const ContentClassifierFeature> GetFeatures();
// Returns the feature with the given name, or Nothing() if no such
// feature exists in the static table.
static Maybe<const ContentClassifierFeature&> GetFeatureByName(
const nsACString& aName);
// Returns whether the named blocking feature is currently active.
static bool IsBlockingFeatureActive(const nsACString& aFeatureName,
bool aIsPrivate);
ContentClassifierResult ClassifyForCancel(
const ContentClassifierRequest& aRequest);
ContentClassifierResult ClassifyForAnnotate(
const ContentClassifierRequest& aRequest);
[[nodiscard]] net::ChannelBlockDecision MaybeCancelChannel(
nsIChannel* aChannel, const ContentClassifierResult& aResult);
void MaybeAnnotateChannel(nsIChannel* aChannel,
const ContentClassifierResult& aResult);
private:
ContentClassifierService();
~ContentClassifierService();
void Init();
static void OnPrefChange(const char* aPref, void* aData);
void InitRSClient();
void ShutdownRSClient();
void RemoveBlocker();
already_AddRefed<nsIAsyncShutdownClient> GetAsyncShutdownBarrier() const;
// aIndependentEngines makes every engine evaluate its own rules in
// isolation; matched_rule is not threaded across engines. Used by the
// annotate phase so MaybeAnnotateChannel can attribute matches to every
// feature whose rules actually fired. The cancel phase passes false so
// trailing exception engines see the propagated matched_rule.
ContentClassifierResult ClassifyWithEngines(
const nsTArray<RefPtr<ContentClassifierEngine>>& aEngines,
const ContentClassifierRequest& aRequest, bool aIndependentEngines);
// Take a fresh pref snapshot, decide which active features need to be
// (re)built — either because they have no engine yet, or because one of
// their mListIds appears in aUpdated/aRemoved — and hand the result to
// UpdateFeatures. Main thread only.
void ProcessListChanges(const nsTArray<nsCString>& aUpdated,
const nsTArray<nsCString>& aRemoved);
// This rebuilds the given features,
// according to that preference snapshot. This also means that features that
// aren't referenced by the current pref snapshot are going to have their
// engine destroyed.
void UpdateFeatures(
const nsTArray<const ContentClassifierFeature*>& aFeatures,
EnginesPrefsSnapshot aPreferenceSnapshot);
// Put the given engine into the authoritative map. Doesn't update references
// to this feature's engine elsewhere. See
// PopulateAllActiveEnginesFromPreferenceSnapshot.
nsresult InstallEngine(const nsACString& aFeatureName,
RefPtr<ContentClassifierEngine>&& aEngine)
MOZ_REQUIRES(mLock);
// Get the pointers in each of the arrays used in classification to point to
// the latest version in the authoritative mEngines map.
void PopulateAllActiveEnginesFromPreferenceSnapshot(
const EnginesPrefsSnapshot& aPreferenceSnapshot) MOZ_REQUIRES(mLock);
// Helper for PopulateAllActiveEnginesFromPreferenceSnapshot.
void PopulateActiveEngineListFromFeatureNames(
const nsTArray<nsCString>& aFeatureNames,
nsTArray<RefPtr<ContentClassifierEngine>>& aEngineList)
MOZ_REQUIRES(mLock);
// Remove engines from mEngines that aren't being used by any of the arrays
// used in classification.
void PruneInactiveEngines(const EnginesPrefsSnapshot& aPreferenceSnapshot)
MOZ_REQUIRES(mLock);
// Get a set of which features are being used in classification
nsTHashSet<nsCString> ActiveFeatureNames(
const EnginesPrefsSnapshot& aPreferenceSnapshot);
// Helper type to grab a list of rules to be built into an engine
using EngineRulesPromise = MozPromise<nsTArray<nsCString>, nsresult,
/* IsExclusive = */ true>;
// Grab the list of rules for a given feature. Hits remote settings, unless it
// is a test_* feature, then it delegates to FetchEngineDataForTestFeature.
RefPtr<EngineRulesPromise> FetchEngineDataForFeature(
const ContentClassifierFeature& aFeature);
// Fetch and parse the resourses for a test_* feature, returning a list of
// rules to build
RefPtr<EngineRulesPromise> FetchEngineDataForTestFeature(
const ContentClassifierFeature& aFeature);
static StaticRefPtr<ContentClassifierService> sInstance;
static bool sEnabled;
mozilla::Mutex mLock MOZ_UNANNOTATED;
InitPhase mInitPhase MOZ_GUARDED_BY(mLock);
// Feature-keyed engines built from the new engines/engines.pbmode prefs.
// Each feature's engine is constructed once from the union of rules in
// its mListIds.
nsTHashMap<nsCStringHashKey, RefPtr<ContentClassifierEngine>> mEngines
MOZ_GUARDED_BY(mLock);
// Per-feature monotonic version counter, bumped each time
// UpdateFeatures decides to rebuild that feature's engine. The
// version is captured into the build closure at dispatch time;
// when the closure later runs it skips installation for any feature
// whose recorded version no longer matches the current one (i.e. a
// newer rebuild has been issued since), so two racing rebuilds
// can't write each other's stale results.
nsTHashMap<nsCStringHashKey, uint64_t> mFeatureVersions MOZ_GUARDED_BY(mLock);
// Global update generation. Bumped under mLock on every UpdateFeatures
// call (including the empty-features path). Each closure captures
// its generation at dispatch time and only runs Populate / Prune /
// Notify when its generation equals mUpdateGeneration — i.e., no
// newer UpdateFeatures has been issued. This prevents an older
// closure with a stale snapshot from clobbering active-engine lists
// populated by a newer call's snapshot.
uint64_t mUpdateGeneration MOZ_GUARDED_BY(mLock) = 0;
// Engines to consult at classify time, split by phase (Cancel/Annotate)
// and PBM-ness. Refreshed alongside mEngines whenever the engines
// selection changes.
nsTArray<RefPtr<ContentClassifierEngine>> mCancelEngines
MOZ_GUARDED_BY(mLock);
nsTArray<RefPtr<ContentClassifierEngine>> mCancelEnginesPBM
MOZ_GUARDED_BY(mLock);
nsTArray<RefPtr<ContentClassifierEngine>> mAnnotateEngines
MOZ_GUARDED_BY(mLock);
nsTArray<RefPtr<ContentClassifierEngine>> mAnnotateEnginesPBM
MOZ_GUARDED_BY(mLock);
// RemoteSettings client for fetching filter lists. All reads and
// writes must happen on the main thread; each call site asserts.
nsCOMPtr<nsIContentClassifierRemoteSettingsClient> mRSClient;
// Serial background task queue used for the CPU-heavy half of engine
// rebuilds (BuildEngineFromRules) plus the lock-holding install /
// populate / prune phase. Created in Init(); drained and cleared in
// BlockShutdown before RemoveBlocker runs.
nsCOMPtr<nsISerialEventTarget> mBuildThread;
};
} // namespace mozilla
#endif // mozilla_ContentClassifierService_h