Skip to content

Commit 9cc0493

Browse files
committed
util/cell: make read/write safe by using critical_section
Removes the need to propagate the ugly unsafe blocks upwards. We add the Copy requirement in SyncCell read to avoid issues with drop semantics (e.g. a missed drop that was expected in write(), or a double-drop of the copy and original in read()).
1 parent d02d139 commit 9cc0493

File tree

7 files changed

+32
-38
lines changed

7 files changed

+32
-38
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ foreach(type ${RUST_LIBS})
400400
# We build the rust standard libs so that everything from the rust runtime
401401
# is included (like eh_personality).
402402
if(type STREQUAL "c-unit-tests")
403-
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std)
403+
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std=std,panic_abort)
404404
endif()
405405
set(lib ${RUST_BINARY_DIR}/feature-${type}/${RUST_TARGET_ARCH_DIR}/${RUST_PROFILE}/libbitbox02_rust_c.a)
406406
# The dummy output is to always trigger rebuild (cargo is clever enough to

src/rust/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rust/bitbox02-rust-c/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ firmware = [
8888
testing = ["bitbox02-rust/testing", "bitbox02/testing"]
8989

9090
# Active when the Rust code is compiled to be linked into the C unit tests and simulator.
91-
c-unit-testing = ["bitbox02-rust/c-unit-testing", "bitbox02/c-unit-testing"]
91+
c-unit-testing = ["bitbox02-rust/c-unit-testing", "bitbox02/c-unit-testing", "util/c-unit-testing"]
9292

9393
app-ethereum = [
9494
# enable this feature in the deps

src/rust/bitbox02-rust-c/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ extern crate util;
3838
// Whenever execution reaches somewhere it isn't supposed to rust code will "panic". Our panic
3939
// handler will print the available information on the screen and over RTT. If we compile with
4040
// `panic=abort` this code will never get executed.
41-
#[cfg(not(test))]
42-
#[cfg(not(feature = "testing"))]
4341
#[cfg_attr(feature = "bootloader", allow(unused_variables))]
42+
#[cfg(not(any(test, feature = "testing", feature = "c-unit-testing")))]
4443
#[panic_handler]
4544
fn panic(info: &core::panic::PanicInfo) -> ! {
4645
#[cfg(feature = "firmware")]

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub use bitbox02::keystore::SignResult;
2424
use bitbox02::{keystore, securechip};
2525

2626
use util::bip32::HARDENED;
27-
use util::cell::SyncUnsafeCell;
27+
use util::cell::SyncCell;
2828

2929
use crate::secp256k1::SECP256K1;
3030

@@ -33,12 +33,12 @@ use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256, sha512};
3333
/// Length of a compressed secp256k1 pubkey.
3434
const EC_PUBLIC_KEY_LEN: usize = 33;
3535

36-
static ROOT_FINGERPRINT: SyncUnsafeCell<Option<[u8; 4]>> = SyncUnsafeCell::new(None);
36+
static ROOT_FINGERPRINT: SyncCell<Option<[u8; 4]>> = SyncCell::new(None);
3737

3838
/// Locks the keystore (resets to state before `unlock()`).
3939
pub fn lock() {
4040
keystore::_lock();
41-
unsafe { ROOT_FINGERPRINT.write(None) }
41+
ROOT_FINGERPRINT.write(None)
4242
}
4343

4444
/// Returns false if the keystore is unlocked (unlock() followed by unlock_bip39()), true otherwise.
@@ -74,9 +74,7 @@ pub async fn unlock_bip39(
7474
keystore::unlock_bip39_finalize(bip39_seed.as_slice().try_into().unwrap())?;
7575

7676
// Store root fingerprint.
77-
unsafe {
78-
ROOT_FINGERPRINT.write(Some(root_fingerprint));
79-
}
77+
ROOT_FINGERPRINT.write(Some(root_fingerprint));
8078
Ok(())
8179
}
8280

@@ -208,7 +206,7 @@ pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
208206
if is_locked() {
209207
return Err(());
210208
}
211-
unsafe { ROOT_FINGERPRINT.read().ok_or(()).map(|fp| fp.to_vec()) }
209+
ROOT_FINGERPRINT.read().ok_or(()).map(|fp| fp.to_vec())
212210
}
213211

214212
/// Stretches the given encryption_key using the securechip. The resulting key is used to encrypt

src/rust/util/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ license = "Apache-2.0"
2323
[dependencies]
2424
num-bigint = { workspace = true, default-features = false }
2525
rtt-target = { version = "0.6.1", optional = true }
26-
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"], optional = true }
26+
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
2727
hex = {workspace = true}
2828
sha2 = { workspace = true, optional = true }
2929
p256 = { version = "0.13.2", default-features = false, features = ["arithmetic", "ecdsa"], optional = true }
3030
bitcoin = {workspace = true}
31+
critical-section = { version = "1.2.0", default-features = false, features = [] }
3132

3233
[features]
33-
rtt = ["dep:rtt-target", "dep:cortex-m"]
34-
testing = []
35-
c-unit-testing = []
34+
rtt = ["dep:rtt-target"]
35+
testing = ["critical-section/std"]
36+
c-unit-testing = ["critical-section/std"]
3637
firmware = []

src/rust/util/src/cell.rs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,43 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Same as `core::sync::SyncUnsafeCell`, which is still in nightly. Can remove once it is stable.
16-
pub struct SyncUnsafeCell<T: ?Sized> {
15+
// Link library to provide symbols necessary for critical section even if code isn't called
16+
// explicitly. See
17+
// https://github.com/rust-embedded/critical-section/blob/bc0d1736c3b14e340c1b0c29042edb3e6bffe6b8/README.md#undefined-reference-errors
18+
#[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
19+
#[allow(unused_imports)]
20+
use cortex_m as _;
21+
22+
// A cell useful for global mutable state.
23+
pub struct SyncCell<T: ?Sized> {
1724
value: core::cell::UnsafeCell<T>,
1825
}
1926

2027
// Implement Sync if the wrapped type is Sync.
21-
unsafe impl<T: ?Sized + Sync> Sync for SyncUnsafeCell<T> {}
28+
unsafe impl<T: ?Sized + Sync> Sync for SyncCell<T> {}
2229

23-
impl<T> SyncUnsafeCell<T> {
30+
impl<T> SyncCell<T> {
2431
pub const fn new(val: T) -> Self {
25-
SyncUnsafeCell {
32+
SyncCell {
2633
value: core::cell::UnsafeCell::new(val),
2734
}
2835
}
2936

3037
/// Reads the value from `self` without moving it. This leaves the
3138
/// memory in `self` unchanged.
32-
///
33-
/// # Safety
34-
///
35-
/// This is unsafe because it allows accessing the interior value without
36-
/// synchronization. The caller must ensure no other code is currently
37-
/// writing to this cell during the read operation.
38-
pub unsafe fn read(&self) -> T
39+
pub fn read(&self) -> T
3940
where
40-
T: Sized,
41+
T: Sized + Copy,
4142
{
42-
unsafe { self.value.get().read() }
43+
critical_section::with(|_| unsafe { self.value.get().read() })
4344
}
4445

4546
/// Overwrites a memory location with the given value without reading or
4647
/// dropping the old value.
47-
///
48-
/// # Safety
49-
///
50-
/// This is unsafe because it allows accessing the interior value without
51-
/// synchronization. The caller must ensure no other code is currently
52-
/// accessing this cell (either reading or writing) during the write operation.
53-
pub unsafe fn write(&self, val: T)
48+
pub fn write(&self, val: T)
5449
where
55-
T: Sized,
50+
T: Sized + Copy,
5651
{
57-
unsafe { self.value.get().write(val) }
52+
critical_section::with(|_| unsafe { self.value.get().write(val) })
5853
}
5954
}

0 commit comments

Comments
 (0)