Draft: Bug 41165: Onion rewrite static pref causes a crash in debug builds
Merge Info
Related Issues
- #40458 (closed)
- closes #41165 (closed)
Backporting
Timeline
-
Immediate: patchset needed as soon as possible -
Next Minor Stable Release: patchset that needs to be verified in nightly before backport -
Eventually: patchset that needs to be verified in alpha before backport -
No Backport (preferred): patchset for the next major stable
(Optional) Justification
-
Emergency security update: patchset fixes CVEs, 0-days, etc -
Censorship event: patchset enables censorship circumvention -
Critical bug-fix: patchset fixes a bug in core-functionality -
Consistency: patchset which would make development easier if it were in both the alpha and release branches; developer tools, build system changes, etc -
Sponsor required: patchset required for sponsor -
Other: please explain
Merging
-
Merge to tor-browser
-!fixups
totor-browser
-specific commits, new features, security backports -
Merge to base-browser
-!fixups
tobase-browser
-specific commits, new features to be shared withmullvad-browser
, and security backports-
NOTE: if your changeset includes patches to both
base-browser
andtor-browser
please clearly label in the change description which commits should be cherry-picked tobase-browser
after merging
-
NOTE: if your changeset includes patches to both
Issue Tracking
-
Link resolved issues with appropriate Release Prep issue for changelog generation
Review
Request Reviewer
-
Request review from an applications developer depending on modified system: -
NOTE: if the MR modifies multiple areas, please
/cc
all the relevant reviewers (since gitlab only allows 1 reviewer) - android : dan
- misc/other : pierov
-
NOTE: if the MR modifies multiple areas, please
Change Description
This is a merge request to fix a bug that manifests in debug builds. I performed only a basic unit test on Android platform with old Tor Browser version 12.0.2 based on Firefox 102.7.0esr, but it's still relevant for version 115.2.0esr, though some names are different [12][13][14]. Essentially it's a patch for MR "Bug 40458: Implement .tor.onion aliases" !262 (merged) .
The logic works as follow:
A. In main process string "browser.urlbar" is added to sParentOnlyPrefBranchList[] [1].
B. Preference "browser.urlbar.onionRewrites.enabled" isn't added to gSharedMap in Preferences::EnsureSnapshot() [2] because ShouldSanitizePreference_Impl() finds its left part in sParentOnlyPrefBranchList[] [3].
C. In gpu process (GPUProcessImpl::Init() [4]) pref_SharedLookup() doesn't find value of preference "browser.urlbar.onionRewrites.enabled" in gSharedMap [5] which leads to signal SIGSEGV in macro MOZ_ASSERT() with message "Failed accessing browser.urlbar.onionRewrites.enabled" [6]. Call stack:
pref_SharedLookup() [5]
Internals::GetSharedPrefValue() [7]
macro ALWAYS_PREF() (also calls macro MOZ_ASSERT()) [6]
StaticPrefList_browser.h (generated from StaticPrefList.yaml) [8][9]
InitStaticPrefsFromShared() [6]
One of possible solutions, that's proposed in this MR, can be moving preference "browser.urlbar.onionRewrites.enabled" from StaticPrefList.yaml to 001-base-profile.js where other preferences, starting with "browser.urlbar", reside [10], and replacing its reference StaticPrefs::browser_urlbar_onionRewrites_enabled() in OnionAliasService::GetOnionAlias() [11] with Preferences::GetBool("browser.urlbar.onionRewrites.enabled").
Code snippets (line numbers in snippets 1-11 are from git tag tor-browser-102.7.0esr-12.0-1-build1, and 13-14 from tor-browser-115.2.0esr-13.0-1-build2):
1. modules/libpref/Preferences.cpp:5774
// These prefs are not useful in child processes.
static const PrefListEntry sParentOnlyPrefBranchList[] = {
// Remove prefs with user data
...
PREF_LIST_ENTRY("browser.urlbar"),
2. modules/libpref/Preferences.cpp:3690
FileDescriptor Preferences::EnsureSnapshot(size_t* aSize) {
...
if (!gSharedMap) {
SharedPrefMapBuilder builder;
nsTArray<Pref*> toRepopulate;
NameArena* newPrefNameArena = new NameArena();
for (auto iter = HashTable()->modIter(); !iter.done(); iter.next()) {
if (!ShouldSanitizePreference(iter.get().get(), true)) {
iter.get()->AddToMap(builder);
} else {
Pref* pref = iter.getMutable().release();
pref->RelocateName(newPrefNameArena);
toRepopulate.AppendElement(pref);
}
}
3. modules/libpref/Preferences.cpp:5840
static bool ShouldSanitizePreference_Impl(const T& aPref,
bool aIsDestWebContentProcess) {
...
// First check against the denylist, the denylist is used for
// all subprocesses to reduce IPC traffic.
for (const auto& entry : sParentOnlyPrefBranchList) {
if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
return true;
4. gfx/ipc/GPUProcessImpl.cpp:28
bool GPUProcessImpl::Init(int aArgc, char* aArgv[]) {
5. modules/libpref/Preferences.cpp:1605
Maybe<PrefWrapper> pref_SharedLookup(const char* aPrefName) {
MOZ_DIAGNOSTIC_ASSERT(gSharedMap, "gSharedMap must be initialized");
if (Maybe<SharedPrefMap::Pref> pref = gSharedMap->Get(aPrefName)) {
return Some(*pref);
}
return Nothing();
}
6. modules/libpref/Preferences.cpp:5670
static void InitStaticPrefsFromShared() {
...
#define ALWAYS_PREF(name, base_id, full_id, cpp_type, default_value) \
{ \
StripAtomic<cpp_type> val; \
if (!XRE_IsParentProcess() && IsString<cpp_type>::value && \
sCrashOnBlocklistedPref) { \
MOZ_DIAGNOSTIC_ASSERT( \
!ShouldSanitizePreference(name, XRE_IsContentProcess()), \
"Should not access the preference '" name "' in Content Processes"); \
} \
DebugOnly<nsresult> rv = Internals::GetSharedPrefValue(name, &val); \
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed accessing " name); \
StaticPrefs::sMirror_##full_id = val; \
}
...
#include "mozilla/StaticPrefListAll.h"
7. modules/libpref/Preferences.cpp:4589
static nsresult GetSharedPrefValue(const char* aName, T* aResult) {
nsresult rv = NS_ERROR_UNEXPECTED;
if (Maybe<PrefWrapper> pref = pref_SharedLookup(aName)) {
rv = pref->GetValue(PrefValueKind::User, aResult);
...
}
return rv;
}
8. obj-aarch64-linux-android/dist/include/mozilla/StaticPrefList_browser.h
Line 1
// This file was generated by generate_static_pref_list.py from modules/libpref/init/StaticPrefList.yaml. DO NOT EDIT.
...
Line 604
ALWAYS_PREF(
"browser.urlbar.onionRewrites.enabled",
browser_urlbar_onionRewrites_enabled,
browser_urlbar_onionRewrites_enabled,
RelaxedAtomicBool, true
)
9. modules/libpref/init/StaticPrefList.yaml:1541
- name: browser.urlbar.onionRewrites.enabled
type: RelaxedAtomicBool
value: true
mirror: always
10. browser/app/profile/001-base-profile.js:99
pref("browser.urlbar.suggest.searches", false);
pref("browser.urlbar.suggest.quicksuggest.nonsponsored", false);
pref("browser.urlbar.suggest.quicksuggest.sponsored", false);
11. netwerk/dns/OnionAliasService.cpp:78
OnionAliasService::GetOnionAlias(const nsACString& aShortHostname,
nsACString& aLongHostname) {
aLongHostname = aShortHostname;
if (mozilla::StaticPrefs::browser_urlbar_onionRewrites_enabled() &&
StringEndsWith(aShortHostname, ".tor.onion"_ns)) {
nsAutoCString* alias = nullptr;
12. sRestrictFromWebContentProcesses[] instead of sParentOnlyPrefBranchList[], and ShouldSanitizePreference() instead of ShouldSanitizePreference_Impl().
13. modules/libpref/Preferences.cpp:6035
// A preference is 'sanitized' (i.e. not sent to web content processes) if
// one of two criteria are met:
// 1. The pref name matches one of the prefixes in the following list
// 2. The pref is dynamically named (i.e. not specified in all.js or
// StaticPrefList.yml), a string pref, and it is NOT exempted in
// sDynamicPrefOverrideList
//
// This behavior is codified in ShouldSanitizePreference() below
static const PrefListEntry sRestrictFromWebContentProcesses[] = {
// Remove prefs with user data
...
PREF_LIST_ENTRY("browser.urlbar"),
14. modules/libpref/Preferences.cpp:6165
static bool ShouldSanitizePreference(const Pref* const aPref) {
// In the parent process, we use a heuristic to decide if a pref
// value should be sanitized before sending to subprocesses.
...
// First check against the denylist.
// The services pref is an annoying one - it's much easier to blocklist
// the whole branch and then add this one check to let this one annoying
// pref through.
for (const auto& entry : sRestrictFromWebContentProcesses) {
if (strncmp(entry.mPrefBranch, prefName, entry.mLen) == 0) {
const auto* p = prefName; // This avoids clang-format doing ugly things.
return !(strncmp("services.settings.clock_skew_seconds", p, 36) == 0 ||
strncmp("services.settings.last_update_seconds", p, 37) == 0 ||
strncmp("services.settings.server", p, 24) == 0);
}
}