From b6cffdf94266cbfac3649d30f9f89190f9e6a2db Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 12 Oct 2026 13:22:00 -0500 Subject: [PATCH] fix(lite-backend): catch panics at the wallet-open C entry points litelib_initialize_new, litelib_initialize_new_from_phrase, litelib_initialize_existing and litelib_check_server_online had no catch_unwind (only litelib_execute did), so any panic below them unwound across the C ABI into the GUI. With rustc 1.63 that is undefined behaviour; in practice the process aborts with "fatal runtime error: failed to initiate panic, error 5". The simplest trigger is a malformed user-entered server URL: get_server_or_default unwraps the http::Uri parse before anything else runs. - ffi_guard (next to error_cstring) runs a body under catch_unwind and turns a panic into "Error: Rust panic in ", which the C++ bridge already treats as a failed call. The three init entry points keep their extern signatures and #[no_mangle] and call ffi_guard; their old bodies are now private *_impl functions. - litelib_check_server_online wraps its body the same way and returns false on a panic. - litelib_initialize_existing opens the wallet through read_from_disk_reporting. After a recovery from the .bak it returns "OK: recovered from backup; damaged file kept as " instead of "OK". The C++ side treats any non-empty, non-error string as a successful open, so this is ABI-compatible. No ABI or symbol change: the exported symbol inventory (lite-backend-symbols.txt) is identical. panic = "unwind" stays in the release profile, and every guard depends on it. Co-Authored-By: Claude Opus 5.5 --- third_party/silentdragonxlite/lib/src/lib.rs | 39 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/third_party/silentdragonxlite/lib/src/lib.rs b/third_party/silentdragonxlite/lib/src/lib.rs index 1b1fcbd..0267f21 100644 --- a/third_party/silentdragonxlite/lib/src/lib.rs +++ b/third_party/silentdragonxlite/lib/src/lib.rs @@ -22,6 +22,15 @@ fn error_cstring(msg: &str) -> *mut c_char { safe_cstring(&format!("Error: {}", msg)).into_raw() } +/// Run a C entry point's body and turn a Rust panic into an "Error: ..." string (C244): a panic must never unwind +/// across the C ABI. This relies on panic = "unwind" (lib/Cargo.toml [profile.release]). +fn ffi_guard *mut c_char>(name: &str, f: F) -> *mut c_char { + match panic::catch_unwind(panic::AssertUnwindSafe(f)) { + Ok(p) => p, + Err(_) => error_cstring(&format!("Rust panic in {}", name)), + } +} + // We'll use a MUTEX to store a global lightclient instance, // so we don't have to keep creating it. We need to store it here, in rust // because we can't return such a complex structure back to C++ @@ -70,6 +79,10 @@ pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{ /// Create a new wallet and return the seed for the newly created wallet. #[no_mangle] pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char { + ffi_guard("litelib_initialize_new", || litelib_initialize_new_impl(dangerous, server)) +} + +fn litelib_initialize_new_impl(dangerous: bool, server: *const c_char) -> *mut c_char { let server_str = unsafe { assert!(!server.is_null()); @@ -126,6 +139,12 @@ pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> * #[no_mangle] pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char, seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char { + ffi_guard("litelib_initialize_new_from_phrase", + || litelib_initialize_new_from_phrase_impl(dangerous, server, seed, birthday, number, overwrite)) +} + +fn litelib_initialize_new_from_phrase_impl(dangerous: bool, server: *const c_char, + seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char { if server.is_null() || seed.is_null() { println!("Server or seed is null"); return ptr::null_mut(); @@ -197,6 +216,10 @@ pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *c // Initialize a new lightclient and store its value #[no_mangle] pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char { + ffi_guard("litelib_initialize_existing", || litelib_initialize_existing_impl(dangerous, server)) +} + +fn litelib_initialize_existing_impl(dangerous: bool, server: *const c_char) -> *mut c_char { let server_str = unsafe { assert!(!server.is_null()); @@ -212,8 +235,10 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char } }; - let lightclient = match LightClient::read_from_disk(&config) { - Ok(l) => l, + // s2-D14: the core reports a recovery from the backup; pass it on (the C++ side treats any non-error string as + // a successful open). + let (lightclient, recovered) = match LightClient::read_from_disk_reporting(&config) { + Ok(r) => r, Err(e) => { let e_str = CString::new(format!("Error: {}", e)).unwrap(); return e_str.into_raw(); @@ -236,7 +261,10 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); }, }; - let c_str = safe_cstring("OK"); + let c_str = match recovered { + Some(note) => safe_cstring(&format!("OK: {}", note)), + None => safe_cstring("OK"), + }; return c_str.into_raw(); } @@ -286,6 +314,11 @@ pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c // Check is Server Connection is fine #[no_mangle] pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool { + // A malformed server URL panics in get_server_or_default (and get_client); never unwind across the C ABI. + panic::catch_unwind(move || litelib_check_server_online_impl(server)).unwrap_or(false) +} + +fn litelib_check_server_online_impl(server: *const c_char) -> bool { let server_str = unsafe { assert!(!server.is_null());