Skip to content

Commit 9e992ac

Browse files
TonyMassonclaude
andcommitted
Fix critical memory safety issue in Transaction API
PROBLEM: Transaction was storing a raw *mut CBLDatabase pointer without ensuring the underlying Database remains alive. This could lead to use-after-free if the Database was dropped before the Transaction: ```rust let transaction = { let mut db = Database::open("test", None)?; db.begin_transaction()? // db dropped here\! }; transaction.commit()?; // 💥 Use-after-free\! ``` SOLUTION: Transaction now owns a Database clone instead of a raw pointer: - Transaction::new() calls db.clone() which uses reference counting - Database::clone() calls retain() to increment CBLDatabase ref count - When Transaction is dropped, Database is automatically released - CBLDatabase stays alive for the entire Transaction lifetime CHANGES: - Transaction.db_ref: *mut CBLDatabase → Transaction.db: Database - Transaction::new() takes &Database instead of *mut CBLDatabase - All Transaction methods use self.db.get_ref() instead of raw pointer - Memory safety now guaranteed through Rust's ownership system 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 61ca708 commit 9e992ac

File tree

1 file changed

+8
-7
lines changed

1 file changed

+8
-7
lines changed

src/database.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ impl Database {
359359
///
360360
/// For simpler cases, consider using `in_transaction()` instead.
361361
pub fn begin_transaction(&mut self) -> Result<Transaction> {
362-
Transaction::new(self.get_ref())
362+
Transaction::new(self)
363363
}
364364

365365
/// Encrypts or decrypts a database, or changes its encryption key.
@@ -606,20 +606,21 @@ impl Database {
606606
/// A database transaction that can be committed or rolled back.
607607
/// When dropped without being committed, the transaction is automatically rolled back.
608608
pub struct Transaction {
609-
db_ref: *mut CBLDatabase,
609+
db: Database,
610610
committed: bool,
611611
}
612612

613613
impl Transaction {
614-
fn new(db_ref: *mut CBLDatabase) -> Result<Self> {
614+
fn new(db: &Database) -> Result<Self> {
615+
let db_clone = db.clone();
615616
unsafe {
616617
let mut err = CBLError::default();
617-
if !CBLDatabase_BeginTransaction(db_ref, &mut err) {
618+
if !CBLDatabase_BeginTransaction(db_clone.get_ref(), &mut err) {
618619
return failure(err);
619620
}
620621
}
621622
Ok(Transaction {
622-
db_ref,
623+
db: db_clone,
623624
committed: false,
624625
})
625626
}
@@ -628,7 +629,7 @@ impl Transaction {
628629
pub fn commit(mut self) -> Result<()> {
629630
unsafe {
630631
let mut err = CBLError::default();
631-
if !CBLDatabase_EndTransaction(self.db_ref, true, &mut err) {
632+
if !CBLDatabase_EndTransaction(self.db.get_ref(), true, &mut err) {
632633
return failure(err);
633634
}
634635
}
@@ -642,7 +643,7 @@ impl Drop for Transaction {
642643
if !self.committed {
643644
unsafe {
644645
let mut err = CBLError::default();
645-
let _ = CBLDatabase_EndTransaction(self.db_ref, false, &mut err);
646+
let _ = CBLDatabase_EndTransaction(self.db.get_ref(), false, &mut err);
646647
}
647648
}
648649
}

0 commit comments

Comments
 (0)