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-!fixupstotor-browser-specific commits, new features, security backports
- 
Merge to base-browser-!fixupstobase-browser-specific commits, new features to be shared withmullvad-browser, and security backports- 
NOTE: if your changeset includes patches to both base-browserandtor-browserplease clearly label in the change description which commits should be cherry-picked tobase-browserafter 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 /ccall 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);
    }
  }