From 88164752b72a256a53389f0c5d2b7da6cd1faafd Mon Sep 17 00:00:00 2001 From: Johannes Salas Schmidt Date: Thu, 16 Jul 2026 12:09:19 +0200 Subject: [PATCH] don't clone the encdec in the logins sync engine --- CHANGELOG.md | 6 ++++++ components/logins/src/sync/engine.rs | 28 +++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d64561c225..a7f88d5735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ and `LoginStore.wipe_local_except_fxa()`, a variant of `wipe_local()` that preserves the FxA session-credentials login ([#7467](https://github.com/mozilla/application-services/pull/7467)) ([Bug 2053557](https://bugzilla.mozilla.org/show_bug.cgi?id=2053557)) +## 🔧 What's Fixed 🔧 + +### Logins + +- The logins sync engine no longer holds its own long-lived clone of the `EncryptorDecryptor`, fetching it from the store on demand instead. This ensures no dangling reference keeps a foreign (e.g. JS) callback handle alive past `shutdown()`. ([#7478](https://github.com/mozilla/application-services/pull/7478)) + # v153.0 (_2026-06-15_) ## ⚠️ Breaking Changes ⚠️ diff --git a/components/logins/src/sync/engine.rs b/components/logins/src/sync/engine.rs index 81511ea8eb..2e807fca86 100644 --- a/components/logins/src/sync/engine.rs +++ b/components/logins/src/sync/engine.rs @@ -28,7 +28,6 @@ use sync_guid::Guid; pub struct LoginsSyncEngine { pub store: Arc, pub scope: SqlInterruptScope, - pub encdec: Arc, // `Mutex` (rather than `RefCell`) so the engine is `Sync`, which the // Desktop `BridgedEngineAdaptor` requires. Only ever locked briefly. pub staged: Mutex>, @@ -36,18 +35,22 @@ pub struct LoginsSyncEngine { impl LoginsSyncEngine { pub fn new(store: Arc) -> Result { - let db = store.lock_db()?; - let scope = db.begin_interrupt_scope()?; - let encdec = db.encdec.clone(); - drop(db); + let scope = store.lock_db()?.begin_interrupt_scope()?; Ok(Self { store, - encdec, scope, staged: Mutex::new(vec![]), }) } + // on Desktop the `EncryptorDecryptor` owns a foreign + // `PrimaryPasswordAuthenticator` callback, and a long-lived `Arc` clone + // held by the engine would keep that callback alive past + // `LoginDb::shutdown` (which drops the db's own reference). + fn encdec(&self) -> Result> { + Ok(self.store.lock_db()?.encdec.clone()) + } + fn reconcile( &self, records: Vec, @@ -55,6 +58,7 @@ impl LoginsSyncEngine { telem: &mut telemetry::EngineIncoming, ) -> Result { let mut plan = UpdatePlan::default(); + let encdec = self.encdec()?; for mut record in records { self.scope.err_if_interrupted()?; @@ -76,7 +80,7 @@ impl LoginsSyncEngine { upstream, upstream_time, server_now, - self.encdec.as_ref(), + encdec.as_ref(), )?; telem.reconciled(1); } @@ -136,10 +140,11 @@ impl LoginsSyncEngine { ) -> Result> { let mut sync_data = Vec::with_capacity(records.len()); { + let encdec = self.encdec()?; let mut seen_ids: HashSet = HashSet::with_capacity(records.len()); for incoming in records.into_iter() { let id = incoming.envelope.id.clone(); - match SyncLoginData::from_bso(incoming, self.encdec.as_ref()) { + match SyncLoginData::from_bso(incoming, encdec.as_ref()) { Ok(v) => sync_data.push(v), Err(e) => { match e { @@ -265,7 +270,7 @@ impl LoginsSyncEngine { } else { let unknown = row.get::<_, Option>("enc_unknown_fields")?; let mut bso = - EncryptedLogin::from_row(row)?.into_bso(self.encdec.as_ref(), unknown)?; + EncryptedLogin::from_row(row)?.into_bso(db.encdec.as_ref(), unknown)?; bso.envelope.sortindex = Some(DEFAULT_SORTINDEX); bso }) @@ -388,7 +393,8 @@ impl LoginsSyncEngine { .form_action_origin .as_ref() .and_then(|s| util::url_host_port(s)); - let enc_fields = l.decrypt_fields(self.encdec.as_ref())?; + let encdec = self.encdec()?; + let enc_fields = l.decrypt_fields(encdec.as_ref())?; let args = named_params! { ":origin": l.fields.origin, ":http_realm": l.fields.http_realm, @@ -413,7 +419,7 @@ impl LoginsSyncEngine { .query_and_then(args, EncryptedLogin::from_row)? .collect::>>()? { - let this_enc_fields = login.decrypt_fields(self.encdec.as_ref())?; + let this_enc_fields = login.decrypt_fields(encdec.as_ref())?; if enc_fields.username == this_enc_fields.username { return Ok(Some(login)); }