Fix Rust callbacks dereference msg without a null check (rust.nim:272, 317). CStr::from_ptr(msg) is UB if msg == nullptr. The C++ side guards with msg ? ... : "". The Nim side

appears to always pass a non-null pointer, but soundness across an FFI boundary shouldn't hinge on a producer's discipline — the Rust receiver should null-check. Especially
  since the C ABI signature here is the one downstream consumers will rely on indefinitely.
This commit is contained in:
Ivan FB 2026-05-11 09:40:33 +02:00
parent 2f6ef08e6c
commit 24a56032af
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
2 changed files with 30 additions and 20 deletions

View File

@ -12,6 +12,17 @@ struct FfiCallbackResult {
type Pair = Arc<(Mutex<FfiCallbackResult>, Condvar)>;
/// Null-safe conversion from a C string returned by the Nim side.
/// The C ABI permits a null `msg` even though the current producer
/// always passes a non-null pointer; treat null as an empty string.
unsafe fn cstr_to_string(msg: *const c_char) -> String {
if msg.is_null() {
String::new()
} else {
CStr::from_ptr(msg).to_string_lossy().into_owned()
}
}
unsafe extern "C" fn on_result(
ret: c_int,
msg: *const c_char,
@ -21,11 +32,8 @@ unsafe extern "C" fn on_result(
let pair = Arc::from_raw(user_data as *const (Mutex<FfiCallbackResult>, Condvar));
let (lock, cvar) = &*pair;
let mut state = lock.lock().unwrap();
state.payload = Some(if ret == 0 {
Ok(CStr::from_ptr(msg).to_string_lossy().into_owned())
} else {
Err(CStr::from_ptr(msg).to_string_lossy().into_owned())
});
let s = cstr_to_string(msg);
state.payload = Some(if ret == 0 { Ok(s) } else { Err(s) });
cvar.notify_one();
}
@ -60,11 +68,8 @@ unsafe extern "C" fn on_result_async(
let tx = Box::from_raw(
user_data as *mut tokio::sync::oneshot::Sender<Result<String, String>>,
);
let value = if ret == 0 {
Ok(CStr::from_ptr(msg).to_string_lossy().into_owned())
} else {
Err(CStr::from_ptr(msg).to_string_lossy().into_owned())
};
let s = cstr_to_string(msg);
let value = if ret == 0 { Ok(s) } else { Err(s) };
let _ = tx.send(value);
}

View File

@ -258,6 +258,17 @@ proc generateApiRs*(procs: seq[FFIProcMeta], libName: string): string =
lines.add("")
lines.add("type Pair = Arc<(Mutex<FfiCallbackResult>, Condvar)>;")
lines.add("")
lines.add("/// Null-safe conversion from a C string returned by the Nim side.")
lines.add("/// The C ABI permits a null `msg` even though the current producer")
lines.add("/// always passes a non-null pointer; treat null as an empty string.")
lines.add("unsafe fn cstr_to_string(msg: *const c_char) -> String {")
lines.add(" if msg.is_null() {")
lines.add(" String::new()")
lines.add(" } else {")
lines.add(" CStr::from_ptr(msg).to_string_lossy().into_owned()")
lines.add(" }")
lines.add("}")
lines.add("")
lines.add("unsafe extern \"C\" fn on_result(")
lines.add(" ret: c_int,")
lines.add(" msg: *const c_char,")
@ -267,11 +278,8 @@ proc generateApiRs*(procs: seq[FFIProcMeta], libName: string): string =
lines.add(" let pair = Arc::from_raw(user_data as *const (Mutex<FfiCallbackResult>, Condvar));")
lines.add(" let (lock, cvar) = &*pair;")
lines.add(" let mut state = lock.lock().unwrap();")
lines.add(" state.payload = Some(if ret == 0 {")
lines.add(" Ok(CStr::from_ptr(msg).to_string_lossy().into_owned())")
lines.add(" } else {")
lines.add(" Err(CStr::from_ptr(msg).to_string_lossy().into_owned())")
lines.add(" });")
lines.add(" let s = cstr_to_string(msg);")
lines.add(" state.payload = Some(if ret == 0 { Ok(s) } else { Err(s) });")
lines.add(" cvar.notify_one();")
lines.add("}")
lines.add("")
@ -311,11 +319,8 @@ proc generateApiRs*(procs: seq[FFIProcMeta], libName: string): string =
lines.add(" let tx = Box::from_raw(")
lines.add(" user_data as *mut tokio::sync::oneshot::Sender<Result<String, String>>,")
lines.add(" );")
lines.add(" let value = if ret == 0 {")
lines.add(" Ok(CStr::from_ptr(msg).to_string_lossy().into_owned())")
lines.add(" } else {")
lines.add(" Err(CStr::from_ptr(msg).to_string_lossy().into_owned())")
lines.add(" };")
lines.add(" let s = cstr_to_string(msg);")
lines.add(" let value = if ret == 0 { Ok(s) } else { Err(s) };")
lines.add(" let _ = tx.send(value);")
lines.add("}")
lines.add("")