What changed, and why it matters
This patch fixes a timing bug in how the BitBox02 hardware wallet talks to its secure chip (Optiga). Previously, the code could overwrite memory buffers that were still being used by an unfinished, dropped secure-chip operation. That could corrupt data or cause the device to behave unpredictably when secure-chip commands were started and then abandoned. The fix simply makes sure the code checks for and reclaims any leftover detached operation before touching the shared buffers.
Treat this as a security-relevant firmware bug. Apply the patch and verify that concurrent or repeated secure-chip operations no longer trigger buffer corruption. Add regression tests or fuzzing around dropped futures and repeated async secure-chip calls if not already present. Consider whether other async FFI wrappers in the codebase have the same pattern.
Security signals we found
Use-after-free-like pattern on static buffers shared with asynchronous C code
Detached async operation state not reclaimed before buffer reuse
Potential memory corruption or incorrect secure-chip command results
Fix is purely reordering; no new locks or allocations added
Affects cryptographic operations: HMAC, ECDSA sign, symmetric encrypt/decrypt, random generation, key generation
Evidence from the diff
The Rust async wrapper around the Infineon Optiga C API uses static buffers (BUF, INPUT, OUTPUT, etc.) and length cells that are passed as raw pointers to the C library. The C command runs asynchronously and invokes a callback on completion. The Rust wrapper has a Detached state for futures dropped after launching a command. Before this patch, several ops.rs functions mutated those static buffers before calling begin_async_op().await. Because begin_async_op() is what detects a prior Detached operation and either reclaims it or panics on a live concurrent operation, mutating the buffers first created a window where a detached C command still owned the same static memory and a repeated call could clear/overwrite it. The patch moves begin_async_op().await before every static buffer or length-cell mutation, closing the race.
Changed components
src/rust/bitbox-securechip/src/optiga/ops.rsOptiga secure chip async Rust wrapperStatic shared buffers: BUF, INPUT, OUTPUT, MAC, SIGNATURE, HMAC, RANDOM, etc.Functions: util_read_data, crypt_hmac, util_write_data, crypt_symmetric_encrypt, crypt_generate_auth_code, crypt_ecdsa_sign, crypt_hmac_verify, crypt_symmetric_generate_key, crypt_randomInspect captured patch +9 / −9
diff --git a/src/rust/bitbox-securechip/src/optiga/ops.rs b/src/rust/bitbox-securechip/src/optiga/ops.rs
index 21c30f9..23618fb 100644
--- a/src/rust/bitbox-securechip/src/optiga/ops.rs
+++ b/src/rust/bitbox-securechip/src/optiga/ops.rs
@@ -257,11 +257,11 @@ pub(super) async fn util_read_data(oid: u16, offset: u16, out: &mut [u8]) -> Res
let util = unsafe { bitbox_securechip_sys::optiga_util_instance() };
+ let guard = begin_async_op().await;
BUF.clear();
unsafe {
LEN.get().write(requested_len);
}
- let guard = begin_async_op().await;
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_util_read_data(util, oid, offset, BUF.as_mut_ptr(), LEN.get())
})
@@ -297,12 +297,12 @@ pub(super) async fn crypt_hmac(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let guard = begin_async_op().await;
INPUT.copy_from_slice(msg);
MAC.clear();
unsafe {
MAC_LEN.get().write(super::KDF_LEN as u32);
}
- let guard = begin_async_op().await;
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_hmac(
crypt,
@@ -351,8 +351,8 @@ pub(super) async fn util_write_data(
let input_len: u16 = buffer.len().try_into().unwrap();
let util = unsafe { bitbox_securechip_sys::optiga_util_instance() };
- INPUT.copy_from_slice(buffer);
let guard = begin_async_op().await;
+ INPUT.copy_from_slice(buffer);
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_util_write_data(
util,
@@ -384,12 +384,12 @@ pub(super) async fn crypt_symmetric_encrypt(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
let input_len = super::KDF_LEN as u32;
+ let guard = begin_async_op().await;
INPUT.copy_from_slice(plain_data);
OUTPUT.clear();
unsafe {
OUTPUT_LEN.get().write(16);
}
- let guard = begin_async_op().await;
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_symmetric_encrypt(
crypt,
@@ -436,8 +436,8 @@ pub(super) async fn crypt_generate_auth_code(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
- RANDOM.clear();
let guard = begin_async_op().await;
+ RANDOM.clear();
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_generate_auth_code(
crypt,
@@ -474,12 +474,12 @@ pub(super) async fn crypt_ecdsa_sign(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let guard = begin_async_op().await;
DIGEST.copy_from_slice(digest);
SIGNATURE.clear();
unsafe {
SIGNATURE_LEN.get().write(ECDSA_SIGNATURE_MAX_LEN as u16);
}
- let guard = begin_async_op().await;
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_ecdsa_sign(
crypt,
@@ -527,9 +527,9 @@ pub(super) async fn crypt_hmac_verify(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let guard = begin_async_op().await;
INPUT.copy_from_slice(input_data);
HMAC.copy_from_slice(hmac);
- let guard = begin_async_op().await;
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_hmac_verify(
crypt,
@@ -558,12 +558,12 @@ pub(super) async fn crypt_symmetric_generate_key(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let guard = begin_async_op().await;
unsafe {
KEYID
.get()
.write(super::key_id_from_oid(super::OID_AES_SYMKEY));
}
- let guard = begin_async_op().await;
wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_symmetric_generate_key(
crypt,
@@ -597,8 +597,8 @@ pub(super) async fn crypt_random(
let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
- BUF.clear();
let guard = begin_async_op().await;
+ BUF.clear();
let result = wait_with_cleanup(guard, unsafe {
bitbox_securechip_sys::optiga_crypt_random(crypt, rng_type, BUF.as_mut_ptr(), 32)
})
Why this scored 69/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.