rust: since rust 1.64, bindgen can use the c types from core
What changed, and why it matters
This commit updates the BitBox02 firmware's Rust code to use Rust's built-in C type definitions instead of a custom module. The commit message notes a bug: the custom module incorrectly defined 'unsigned int' as 64 bits on 64-bit systems, when it should always be 32 bits. The change removes the buggy custom type definitions and switches to the standard Rust core::ffi types. This is primarily a code-quality and correctness fix, but the wrong type sizes could have caused subtle memory or interface mismatches between Rust and C code, especially during testing on 64-bit computers.
Treat this as a defensive hardening/correctness fix. Verify that the firmware's CI now builds with Rust 1.64+ and that all FFI tests pass on both 32-bit ARM target and 64-bit host test builds. Review any remaining custom C type aliases elsewhere in the codebase for similar width mismatches. No urgent security patch is required, but the change should be included in the next release.
Security signals we found
Custom FFI type aliases with incorrect widths for c_int/c_uint/c_long/c_ulong on 64-bit targets
Switch to core::ffi C types, which match the platform ABI correctly
Addition of explicit pointer casts at FFI call sites to satisfy type checking
Removal of local c_types module reduces future maintenance error surface
Evidence from the diff
The patch deletes src/rust/util/src/c_types.rs, which provided hand-rolled C type aliases. It removes the –ctypes-prefix bindgen option so generated bindings use core::ffi types directly. It then updates call sites to use core::ffi::{c_void, c_char, c_uchar, c_int} and adds .cast() calls where Rust CString pointers are passed to C functions expecting c_char pointers. The commit message explicitly states that the custom c_types module had a bug: c_uint was defined as u64 on x86_64/aarch64, whereas C’s unsigned int is 32 bits even on 64-bit platforms. The same logic applies to c_int, c_long, and c_ulong in the removed module. The fix reduces the risk of ABI mismatches in FFI boundaries.
Changed components
src/rust/util/src/c_types.rs (removed)src/rust/bitbox02-sys/build.rs (bindgen configuration)src/rust/bitbox02-rust-c/src/alloc.rssrc/rust/bitbox02-rust-c/src/lib.rssrc/rust/bitbox02-rust-c/src/sha2.rssrc/rust/bitbox02-rust-c/src/util.rssrc/rust/bitbox02/src/keystore.rssrc/rust/bitbox02/src/lib.rssrc/rust/bitbox02/src/memory.rssrc/rust/bitbox02/src/random.rssrc/rust/bitbox02/src/sd.rssrc/rust/bitbox02/src/ui/types.rssrc/rust/bitbox02/src/ui/ui.rsInspect captured patch +49 / −85
diff --git a/src/rust/bitbox02-rust-c/src/alloc.rs b/src/rust/bitbox02-rust-c/src/alloc.rs
index 3b3698d..14d1b19 100644
--- a/src/rust/bitbox02-rust-c/src/alloc.rs
+++ b/src/rust/bitbox02-rust-c/src/alloc.rs
@@ -15,8 +15,8 @@
struct BB02Allocator;
extern "C" {
- pub fn malloc(size: usize) -> *mut util::c_types::c_void;
- pub fn free(p: *mut util::c_types::c_void);
+ pub fn malloc(size: usize) -> *mut core::ffi::c_void;
+ pub fn free(p: *mut core::ffi::c_void);
}
unsafe impl core::alloc::GlobalAlloc for BB02Allocator {
diff --git a/src/rust/bitbox02-rust-c/src/lib.rs b/src/rust/bitbox02-rust-c/src/lib.rs
index b08b349..5883022 100644
--- a/src/rust/bitbox02-rust-c/src/lib.rs
+++ b/src/rust/bitbox02-rust-c/src/lib.rs
@@ -71,7 +71,7 @@ pub extern "C" fn rust_rtt_flush() {
/// The pointer `ptr` must point to a null terminated string
#[no_mangle]
#[cfg_attr(not(all(feature = "rtt", target_os = "none")), allow(unused))]
-pub unsafe extern "C" fn rust_log(ptr: *const ::util::c_types::c_char) {
+pub unsafe extern "C" fn rust_log(ptr: *const core::ffi::c_char) {
#[cfg(all(feature = "rtt", target_os = "none"))]
{
if ptr.is_null() {
diff --git a/src/rust/bitbox02-rust-c/src/sha2.rs b/src/rust/bitbox02-rust-c/src/sha2.rs
index ddd2c5f..9e81f61 100644
--- a/src/rust/bitbox02-rust-c/src/sha2.rs
+++ b/src/rust/bitbox02-rust-c/src/sha2.rs
@@ -15,9 +15,9 @@
extern crate alloc;
use alloc::boxed::Box;
+use core::ffi::{c_uchar, c_void};
use sha2::Digest;
use sha2::Sha256;
-use util::c_types::{c_uchar, c_void};
/// Result must be freed by calling `rust_sha256_finish()` or `rust_sha256_free()`.
#[no_mangle]
diff --git a/src/rust/bitbox02-rust-c/src/util.rs b/src/rust/bitbox02-rust-c/src/util.rs
index eb28075..5b3b3cc 100644
--- a/src/rust/bitbox02-rust-c/src/util.rs
+++ b/src/rust/bitbox02-rust-c/src/util.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use util::c_types::c_uchar;
+use core::ffi::c_uchar;
/// Zero a buffer using volatile writes. Accepts null-ptr and 0-length buffers and does nothing.
///
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 95101de..b18b0fb 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -262,7 +262,6 @@ pub fn main() -> Result<(), &'static str> {
.args(["--output", &out_path])
.arg("--use-core")
.arg("--with-derive-default")
- .args(["--ctypes-prefix", "util::c_types"])
.args(
ALLOWLIST_FNS
.iter()
diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs
index b8f4f4b..cb1c289 100644
--- a/src/rust/bitbox02/src/keystore.rs
+++ b/src/rust/bitbox02/src/keystore.rs
@@ -66,7 +66,10 @@ pub fn unlock(password: &str) -> Result<(), Error> {
let mut securechip_result: i32 = 0;
match unsafe {
bitbox02_sys::keystore_unlock(
- crate::util::str_to_cstr_vec(password).unwrap().as_ptr(),
+ crate::util::str_to_cstr_vec(password)
+ .unwrap()
+ .as_ptr()
+ .cast(),
&mut remaining_attempts,
&mut securechip_result,
)
@@ -89,7 +92,8 @@ pub fn unlock_bip39(mnemonic_passphrase: &str) -> Result<(), Error> {
bitbox02_sys::keystore_unlock_bip39(
crate::util::str_to_cstr_vec(mnemonic_passphrase)
.unwrap()
- .as_ptr(),
+ .as_ptr()
+ .cast(),
)
} {
Ok(())
@@ -101,7 +105,10 @@ pub fn unlock_bip39(mnemonic_passphrase: &str) -> Result<(), Error> {
pub fn create_and_store_seed(password: &str, host_entropy: &[u8]) -> Result<(), Error> {
match unsafe {
bitbox02_sys::keystore_create_and_store_seed(
- crate::util::str_to_cstr_vec(password).unwrap().as_ptr(),
+ crate::util::str_to_cstr_vec(password)
+ .unwrap()
+ .as_ptr()
+ .cast(),
host_entropy.as_ptr(),
host_entropy.len() as _,
)
@@ -147,7 +154,7 @@ pub fn secp256k1_sign(
host_nonce: &[u8; 32],
) -> Result<SignResult, ()> {
let mut signature = [0u8; 64];
- let mut recid: util::c_types::c_int = 0;
+ let mut recid: core::ffi::c_int = 0;
match unsafe {
bitbox02_sys::keystore_secp256k1_sign(
private_key.as_ptr(),
@@ -196,7 +203,10 @@ pub fn encrypt_and_store_seed(seed: &[u8], password: &str) -> Result<(), Error>
bitbox02_sys::keystore_encrypt_and_store_seed(
seed.as_ptr(),
seed.len(),
- crate::util::str_to_cstr_vec(password).unwrap().as_ptr(),
+ crate::util::str_to_cstr_vec(password)
+ .unwrap()
+ .as_ptr()
+ .cast(),
)
} {
keystore_error_t::KEYSTORE_OK => Ok(()),
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index c383a59..f06255e 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -43,7 +43,7 @@ pub mod securechip;
pub mod spi_mem;
pub mod ui;
-use ::util::c_types::c_int;
+use core::ffi::c_int;
use core::time::Duration;
pub use bitbox02_sys::buffer_t;
@@ -56,7 +56,7 @@ pub fn ug_put_string(x: i16, y: i16, input: &str, inverted: bool) {
bitbox02_sys::UG_PutString(
x,
y,
- crate::util::str_to_cstr_vec(input).unwrap().as_ptr(),
+ crate::util::str_to_cstr_vec(input).unwrap().as_ptr().cast(),
inverted,
);
}
@@ -102,7 +102,7 @@ pub fn delay(duration: Duration) {
pub fn screen_print_debug(msg: &str, duration: i32) {
unsafe {
bitbox02_sys::screen_print_debug(
- crate::util::str_to_cstr_vec(msg).unwrap().as_ptr(),
+ crate::util::str_to_cstr_vec(msg).unwrap().as_ptr().cast(),
duration,
)
}
@@ -229,15 +229,15 @@ pub fn reboot_to_bootloader() -> ! {
#[cfg(any(feature = "testing", feature = "c-unit-testing"))]
pub fn print_stdout(msg: &str) {
unsafe {
- bitbox02_sys::printf(crate::util::str_to_cstr_vec(msg).unwrap().as_ptr());
+ bitbox02_sys::printf(crate::util::str_to_cstr_vec(msg).unwrap().as_ptr().cast());
}
}
#[cfg(any(feature = "testing", feature = "c-unit-testing"))]
pub fn println_stdout(msg: &str) {
unsafe {
- bitbox02_sys::printf(crate::util::str_to_cstr_vec(msg).unwrap().as_ptr());
- bitbox02_sys::printf(crate::util::str_to_cstr_vec("\n").unwrap().as_ptr());
+ bitbox02_sys::printf(crate::util::str_to_cstr_vec(msg).unwrap().as_ptr().cast());
+ bitbox02_sys::printf(crate::util::str_to_cstr_vec("\n").unwrap().as_ptr().cast());
}
}
diff --git a/src/rust/bitbox02/src/memory.rs b/src/rust/bitbox02/src/memory.rs
index fd39381..1a0d3d7 100644
--- a/src/rust/bitbox02/src/memory.rs
+++ b/src/rust/bitbox02/src/memory.rs
@@ -31,7 +31,7 @@ pub struct Error;
pub fn get_device_name() -> String {
let mut name = [0u8; DEVICE_NAME_MAX_LEN + 1];
- unsafe { bitbox02_sys::memory_get_device_name(name.as_mut_ptr()) }
+ unsafe { bitbox02_sys::memory_get_device_name(name.as_mut_ptr().cast()) }
crate::util::str_from_null_terminated(&name[..])
.unwrap()
.into()
@@ -40,7 +40,10 @@ pub fn get_device_name() -> String {
pub fn set_device_name(name: &str) -> Result<(), Error> {
match unsafe {
bitbox02_sys::memory_set_device_name(
- crate::util::str_to_cstr_vec(name).or(Err(Error))?.as_ptr(),
+ crate::util::str_to_cstr_vec(name)
+ .or(Err(Error))?
+ .as_ptr()
+ .cast(),
)
} {
true => Ok(()),
@@ -155,7 +158,8 @@ pub fn multisig_set_by_hash(hash: &[u8], name: &str) -> Result<(), MemoryError>
hash.as_ptr(),
crate::util::str_to_cstr_vec(name)
.or(Err(MemoryError::MEMORY_ERR_INVALID_INPUT))?
- .as_ptr(),
+ .as_ptr()
+ .cast(),
)
} {
MemoryError::MEMORY_OK => Ok(()),
@@ -165,7 +169,9 @@ pub fn multisig_set_by_hash(hash: &[u8], name: &str) -> Result<(), MemoryError>
pub fn multisig_get_by_hash(hash: &[u8]) -> Option<String> {
let mut name = [0u8; MULTISIG_NAME_MAX_LEN + 1];
- match unsafe { bitbox02_sys::memory_multisig_get_by_hash(hash.as_ptr(), name.as_mut_ptr()) } {
+ match unsafe {
+ bitbox02_sys::memory_multisig_get_by_hash(hash.as_ptr(), name.as_mut_ptr().cast())
+ } {
true => Some(
crate::util::str_from_null_terminated(&name[..])
.unwrap()
diff --git a/src/rust/bitbox02/src/random.rs b/src/rust/bitbox02/src/random.rs
index 7c72987..2284d97 100644
--- a/src/rust/bitbox02/src/random.rs
+++ b/src/rust/bitbox02/src/random.rs
@@ -20,7 +20,7 @@ pub fn mcu_32_bytes(out: &mut [u8; 32]) {
#[cfg(not(target_arch = "arm"))]
pub fn mcu_32_bytes(out: &mut [u8; 32]) {
extern "C" {
- fn rand() -> util::c_types::c_int;
+ fn rand() -> core::ffi::c_int;
}
for elem in out.iter_mut() {
diff --git a/src/rust/bitbox02/src/sd.rs b/src/rust/bitbox02/src/sd.rs
index 22dae2f..6815731 100644
--- a/src/rust/bitbox02/src/sd.rs
+++ b/src/rust/bitbox02/src/sd.rs
@@ -41,7 +41,7 @@ pub fn list_subdir(subdir: Option<&str>) -> Result<Vec<String>, ()> {
bitbox02_sys::sd_list_subdir(
&mut list.0,
match c_subdir.as_ref() {
- Some(s) => s.as_ptr(),
+ Some(s) => s.as_ptr().cast(),
None => core::ptr::null(),
},
)
@@ -59,8 +59,8 @@ pub fn list_subdir(subdir: Option<&str>) -> Result<Vec<String>, ()> {
pub fn erase_file_in_subdir(filename: &str, dir: &str) -> Result<(), ()> {
match unsafe {
bitbox02_sys::sd_erase_file_in_subdir(
- str_to_cstr_vec(filename).unwrap().as_ptr(),
- str_to_cstr_vec(dir).unwrap().as_ptr(),
+ str_to_cstr_vec(filename).unwrap().as_ptr().cast(),
+ str_to_cstr_vec(dir).unwrap().as_ptr().cast(),
)
} {
true => Ok(()),
@@ -73,8 +73,8 @@ pub fn load_bin(filename: &str, dir: &str) -> Result<zeroize::Zeroizing<Vec<u8>>
let mut contents_len: usize = 0;
match unsafe {
bitbox02_sys::sd_load_bin(
- str_to_cstr_vec(filename).unwrap().as_ptr(),
- str_to_cstr_vec(dir).unwrap().as_ptr(),
+ str_to_cstr_vec(filename).unwrap().as_ptr().cast(),
+ str_to_cstr_vec(dir).unwrap().as_ptr().cast(),
contents.as_mut_ptr(),
&mut contents_len,
)
@@ -87,8 +87,8 @@ pub fn load_bin(filename: &str, dir: &str) -> Result<zeroize::Zeroizing<Vec<u8>>
pub fn write_bin(filename: &str, dir: &str, data: &[u8]) -> Result<(), ()> {
match unsafe {
bitbox02_sys::sd_write_bin(
- str_to_cstr_vec(filename).unwrap().as_ptr(),
- str_to_cstr_vec(dir).unwrap().as_ptr(),
+ str_to_cstr_vec(filename).unwrap().as_ptr().cast(),
+ str_to_cstr_vec(dir).unwrap().as_ptr().cast(),
data.as_ptr(),
data.len() as _,
true,
diff --git a/src/rust/bitbox02/src/ui/types.rs b/src/rust/bitbox02/src/ui/types.rs
index 02a460e..324ab4a 100644
--- a/src/rust/bitbox02/src/ui/types.rs
+++ b/src/rust/bitbox02/src/ui/types.rs
@@ -83,9 +83,9 @@ impl<'a> ConfirmParams<'a> {
crate::util::str_to_cstr_vec(crate::util::truncate_str(self.body, TRUNCATE_SIZE))
.unwrap();
Survive::new(bitbox02_sys::confirm_params_t {
- title: title_scatch.as_ptr(),
+ title: title_scatch.as_ptr().cast(),
title_autowrap: self.title_autowrap,
- body: body_scratch.as_ptr(),
+ body: body_scratch.as_ptr().cast(),
font: self.font.as_ptr(),
scrollable: self.scrollable,
longtouch: self.longtouch,
@@ -125,7 +125,7 @@ impl<'a> TrinaryInputStringParams<'a> {
.unwrap();
Survive::new(bitbox02_sys::trinary_input_string_params_t {
- title: title_scratch.as_ptr(),
+ title: title_scratch.as_ptr().cast(),
wordlist: match self.wordlist {
None => core::ptr::null(),
Some(wordlist) => wordlist.as_ptr(),
diff --git a/src/rust/bitbox02/src/ui/ui.rs b/src/rust/bitbox02/src/ui/ui.rs
index 79e93d3..6f16742 100644
--- a/src/rust/bitbox02/src/ui/ui.rs
+++ b/src/rust/bitbox02/src/ui/ui.rs
@@ -18,7 +18,7 @@ pub use super::types::{
TrinaryChoiceCb, TrinaryInputStringParams,
};
-use util::c_types::{c_char, c_void};
+use core::ffi::{c_char, c_void};
extern crate alloc;
use alloc::boxed::Box;
@@ -249,7 +249,7 @@ pub fn menu_create(params: MenuParams<'_>) -> Component<'_> {
.collect();
// Step two: collect pointers. This var also has to be valid until menu_create() finishes, or
// the pointer will be invalid.
- let c_words: Vec<*const util::c_types::c_char> =
+ let c_words: Vec<*const core::ffi::c_char> =
words.iter().map(|word| word.as_ptr() as _).collect();
let (select_word_cb, select_word_cb_param) = match params.select_word_cb {
diff --git a/src/rust/util/src/c_types.rs b/src/rust/util/src/c_types.rs
deleted file mode 100644
index 9e49ce7..0000000
--- a/src/rust/util/src/c_types.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 Shift Cryptosecurity AG
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#![allow(non_camel_case_types)]
-#[repr(u8)]
-pub enum c_void {
- #[doc(hidden)]
- __variant,
-}
-pub type c_char = u8;
-pub type c_schar = i8;
-pub type c_uchar = u8;
-pub type c_short = i16;
-pub type c_ushort = u16;
-pub type c_int = i32;
-
-#[cfg(target_arch = "x86_64")]
-pub type c_uint = u64;
-#[cfg(target_arch = "aarch64")]
-pub type c_uint = u64;
-#[cfg(target_arch = "arm")]
-pub type c_uint = u32;
-
-#[cfg(target_arch = "x86_64")]
-pub type c_long = i64;
-#[cfg(target_arch = "aarch64")]
-pub type c_long = i64;
-#[cfg(target_arch = "arm")]
-pub type c_long = i32;
-
-#[cfg(target_arch = "x86_64")]
-pub type c_ulong = u64;
-#[cfg(target_arch = "aarch64")]
-pub type c_ulong = u64;
-#[cfg(target_arch = "arm")]
-pub type c_ulong = u32;
-
-pub type c_longlong = i64;
-pub type c_ulonglong = u64;
diff --git a/src/rust/util/src/lib.rs b/src/rust/util/src/lib.rs
index 680fc57..5d7a2e7 100644
--- a/src/rust/util/src/lib.rs
+++ b/src/rust/util/src/lib.rs
@@ -15,7 +15,6 @@
#![cfg_attr(not(test), no_std)]
pub mod ascii;
pub mod bip32;
-pub mod c_types;
pub mod decimal;
pub mod log;
pub mod name;
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.