owner 1147503 tsimonq2@debian.org
affects 1147503 + src:thunderbird src:rust-rusqlite
thanks
Hello,
I did some digging into this today. I was able to get the relevant backtrace, and I have a good idea on what's happening.
Here's the backtrace:
#0 0x0000000000000000 in ?? ()
#1 0x00007fffde894416 in sqlite3VdbeMemGrow () from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
#2 0x00007fffde895058 in sqlite3VdbeMemSetText () from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
#3 0x00007fffde88a9c3 in ?? () from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
#4 0x00007fffce198d1d in rusqlite::statement::Statement::bind_parameter<&&dyn rusqlite::types::to_sql::ToSql> (self=<optimized out>, param=<optimized out>, ndx=1) at /usr/share/cargo/registry/rusqlite-0.40.2/src/statement.rs:646
#5 0x00007fffce1a9e6c in rusqlite::statement::Statement::bind_parameters<&[&dyn rusqlite::types::to_sql::ToSql; 2]> (self=0x7fffd37fd120, params=0x7fffd37fd2d0) at /usr/share/cargo/registry/rusqlite-0.40.2/src/statement.rs:486
#6 rusqlite::params::{impl#49}::__bind_in<dyn rusqlite::types::to_sql::ToSql> (self=0x7fffd37fd2d0, stmt=0x7fffd37fd120) at /usr/share/cargo/registry/rusqlite-0.40.2/src/params.rs:298
#7 rusqlite::statement::Statement::execute<&[&dyn rusqlite::types::to_sql::ToSql; 2]> (self=0x7fffd37fd120, params=0x7fffd37fd2d0) at /usr/share/cargo/registry/rusqlite-0.40.2/src/statement.rs:109
#8 rusqlite::{impl#3}::execute::{closure#0}<&[&dyn rusqlite::types::to_sql::ToSql; 2]> (stmt=...) at /usr/share/cargo/registry/rusqlite-0.40.2/src/lib.rs:621
#9 core::result::Result<rusqlite::statement::Statement, rusqlite::error::Error>::and_then<rusqlite::statement::Statement, rusqlite::error::Error, usize, rusqlite::{impl#3}::execute::{closure_env#0}<&[&dyn rusqlite::types::to_sql::ToSql; 2]>> (self=..., op=...) at /usr/src/rustc-1.95.0/library/core/src/result.rs:1493
#10 rusqlite::Connection::execute<&[&dyn rusqlite::types::to_sql::ToSql; 2]> (self=0x7fffd37fdb10, sql=..., params=0x7fffd37fd2d0) at /usr/share/cargo/registry/rusqlite-0.40.2/src/lib.rs:621
#11 sequoia_octopus_librnp::wot::WoT::import_validity (managed_by_tb=..., validity=..., tx=<optimized out>) at src/wot.rs:573
#12 sequoia_octopus_librnp::wot::WoT::update (self=0x7fffd37fda78, now=...) at src/wot.rs:497
#13 sequoia_octopus_librnp::wot::WoT::background_thread (self=0x7fffd37fda78) at src/wot.rs:216
#14 0x00007fffce225bbc in sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure#0} () at src/wot.rs:95
#15 std::sys::backtrace::__rust_begin_short_backtrace<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()> (f=...) at /usr/src/rustc-1.95.0/library/std/src/sys/backtrace.rs:166
#16 0x00007fffce236dcc in std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure#0}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()> () at /usr/src/rustc-1.95.0/library/std/src/thread/lifecycle.rs:91
#17 core::panic::unwind_safe::{impl#23}::call_once<(), std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()>> (self=...) at /usr/src/rustc-1.95.0/library/core/src/panic/unwind_safe.rs:274
#18 std::panicking::catch_unwind::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()>>, ()> (data=<error reading variable: Cannot access memory at address 0x0>) at /usr/src/rustc-1.95.0/library/std/src/panicking.rs:581
#19 std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()>>> (f=...) at /usr/src/rustc-1.95.0/library/std/src/panicking.rs:544
#20 std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()>>, ()> (f=...) at /usr/src/rustc-1.95.0/library/std/src/panic.rs:359
#21 std::thread::lifecycle::spawn_unchecked::{closure#1}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()> () at /usr/src/rustc-1.95.0/library/std/src/thread/lifecycle.rs:89
#22 core::ops::function::FnOnce::call_once<std::thread::lifecycle::spawn_unchecked::{closure_env#1}<sequoia_octopus_librnp::wot::{impl#1}::background_thread_start::{closure_env#0}, ()>, ()> () at /usr/src/rustc-1.95.0/library/core/src/ops/function.rs:250
#23 0x00007fffce8105df in alloc::boxed::{impl#31}::call_once<(), (dyn core::ops::function::FnOnce<(), Output=()> + core::marker::Send), alloc::alloc::Global> (self=..., args=<optimized out>) at library/alloc/src/boxed.rs:2240
#24 std::sys::thread::unix::{impl#2}::new::thread_start (data=data@entry=0x7fffd3bfeee0) at library/std/src/sys/thread/unix.rs:118
#25 0x00005555555815b0 in set_alt_signal_stack_and_start (params=<optimized out>) at ./mozglue/interposers/pthread_create_interposer.cpp:81
#26 0x00007ffff7989058 in ?? () from /usr/lib/x86_64-linux-gnu/libc.so.6
#27 0x00007ffff7a07218 in ?? () from /usr/lib/x86_64-linux-gnu/libc.so.6
The important part of the trace starts at frame #11.
sequoia-octopus-librnp has its own src/wot.rs, and this is the Web-of-Trust background thread. WoT::background_thread() calls WoT::update(), which eventually calls WoT::import_validity(). The latter performs SQL statements against Thunderbird's OpenPGP database. The Octopus crate is explicitly a Thunderbird RNP replacement and is built as a cdylib; its upstream manifest also has a direct rusqlite dependency.
The relevant call chain from the backtrace is therefore:
sequoia_octopus_librnp::wot::WoT::background_thread()
-> WoT::update()
-> WoT::import_validity()
-> rusqlite::Connection/Transaction::execute()
-> rusqlite::Statement::bind_parameter()
-> sqlite3_bind_text64()
The last Rust frame is particularly useful. In rusqlite 0.40.2, src/statement.rs:646 is:
ValueRef::Text(s) => unsafe {
let (c_str, len, destructor) = str_for_sqlite(s);
ffi::sqlite3_bind_text64(
ptr,
ndx as c_int,
c_str,
len,
destructor,
ffi::SQLITE_UTF8 as _, // TODO SQLITE_UTF8_ZT
)
},
This is the exact call site shown by the backtrace.
The next three frames are the interesting part:
#3 ... from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
#2 sqlite3VdbeMemSetText() from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
#1 sqlite3VdbeMemGrow() from /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
So the sqlite3_bind_text64() call from rusqlite is actually executing inside Debian's system libsqlite3.so.0.
That matters because Thunderbird also ships its own private SQLite implementation as /usr/lib/thunderbird/libmozsqlite3.so. I checked the exported symbol list shipped by Thunderbird. Its sqlite.symbols file explicitly exports:
sqlite3_bind_text
sqlite3_result_text
but does not export:
sqlite3_bind_text64
sqlite3_result_text64
This means sqlite3_bind_text64 is not part of the exported libmozsqlite3.so ABI.
SQLite itself does have the sqlite3_bind_text64() API, so this is not an SQLite API removal. The issue is the particular ABI exposed by Thunderbird's embedded SQLite.
The backtrace is consistent with a symbol-resolution/ABI mismatch: rusqlite requests sqlite3_bind_text64(), but the implementation that actually runs is the system SQLite implementation, while Thunderbird has a separate embedded SQLite library in the same process with a deliberately narrower exported API.
The crash then occurs entirely inside the system SQLite implementation:
sqlite3_bind_text64()
-> sqlite3VdbeMemSetText()
-> sqlite3VdbeMemGrow()
-> NULL/invalid function call at 0x0
In other words, this is not an ordinary Rust panic or an SQLite error return. We are dereferencing invalid state inside SQLite's internal VDBE memory machinery.
This also explains why simply having both SQLite libraries installed is not harmless here. Octopus is a shared library loaded into Thunderbird rather than a standalone application, and its unresolved SQLite symbols participate in Thunderbird's existing ELF symbol namespace.
The proposed Debian fix therefore changes the text-binding call in rusqlite from:
sqlite3_bind_text64(...)
to:
sqlite3_bind_text(...)
The latter is explicitly exported by Thunderbird's libmozsqlite3.so, so this avoids requesting the unexported sqlite3_bind_text64 symbol when the Rust code is loaded into Thunderbird.
I am also applying the analogous change from sqlite3_result_text64() to sqlite3_result_text(), because Thunderbird's exported SQLite ABI likewise contains sqlite3_result_text but not sqlite3_result_text64.
I think the longer-term question is whether Thunderbird's sqlite.symbols should export the two *_text64 entry points instead. If they are intended to be part of the supported embedded SQLite ABI, adding them there would avoid requiring downstream Rust consumers to work around the restricted export list.
For the immediate Debian issue, however, using the SQLite entry points that Thunderbird actually exports appears to be the minimal fix for the Octopus/Thunderbird crash.
I'm uploading this fix now, please do follow up if the issue isn't fixed. I'm able to see that it's fixed on my end, but it's possible your setup is different.
Best regards,
Simon Quigley
tsimonq2@debian.org