What changed, and why it matters
This commit adds a new first-stage bootloader component called bitbox03-boot0 for an upcoming BitBox03 hardware wallet. It is purely additive development code: it introduces build targets, a linker script, flashing scripts, and a small Rust program that validates and then launches the next-stage bootloader (boot1). There is no indication of a bug fix or security patch, and no disclosed vulnerability.
No security action required; treat as normal feature review. If auditing the new bootloader, focus on vector_table_from_image_header and bootload implementations in bitbox-boot-utils, and verify that BOOT1_ADDR/BOOT1_MAX_LEN and RAM_ADDR/RAM_LEN constraints are correct for the board memory layout.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit creates src/rust/bins/bitbox03-boot0, a no_std Rust binary for the STM32U5A9J-DK board. It validates the boot1 image header via bitbox_boot_utils::vector_table_from_image_header and, if valid, chains to boot1 via bitbox_boot_utils::bootload. Supporting changes move rustflags to the workspace root, add Cargo aliases, include the new crate in the workspace, extend CI artifact collection, and add OpenOCD/GDB helper scripts. The change is feature addition, not a security-relevant patch.
Changed components
bitbox03-boot0 first-stage bootloaderCargo workspace configurationCI build workflowOpenOCD/GDB flashing/debug scriptsInspect captured patch +214 / −7
### .cargo/config.toml
@@ -1,2 +1,18 @@
# Include host-specific Cargo settings generated by scripts/bootstrap-cargo-config.
include = [{ path = "config.local.toml", optional = true }]
+
+[build]
+# Use our own copy of libsecp256k1-zpk instead of the one bundled with secp256k1-sys, also in `cargo
+# test` and other cargo-based builds. This is replicated in src/CMakeLists.txt,
+# test/simulator-graphical*/CMakeLists.txt for CMake-based builds.
+# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
+rustflags = ["--cfg", "rust_secp_no_symbol_renaming"]
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+rustflags = [
+ "--cfg", "rust_secp_no_symbol_renaming",
+ "-C", "linker-plugin-lto",
+ # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
+ # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
+ "-C", "link-arg=--nmagic",
+]
### .github/workflows/ci-common.yml
@@ -210,6 +210,7 @@ jobs:
- firmware-blupgrade-bitbox02-btconly-development
- factory-setup
- firmware-debug
+ - bitbox03-boot0
- simulator
- simulator-graphical
- simulator-graphical-bb03
@@ -241,7 +242,15 @@ jobs:
run: ./.ci/check-unwanted-symbols
- name: Print hashes
- run: sha256sum build*/bin/*
+ run: |
+ {
+ for dir in build*/bin src/rust/target/thumbv8m.main-none-eabihf/debug; do
+ if [ -d "$dir" ]; then
+ find "$dir" -maxdepth 1 -type f -print0
+ fi
+ done
+ true
+ } | xargs -0 -r sha256sum
- name: Upload artifact
if: github.event_name == 'push' && !cancelled()
@@ -253,6 +262,7 @@ jobs:
build*/bin/*.elf
build*/bin/*.map
build*/bin/simulator*
+ src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-*
# Build simulators for macos aarch64
build-macos:
### Makefile
@@ -258,3 +258,17 @@ clean:
# When you vendor rust libs avoid duplicates
vendor-rust-deps:
./external/vendor-rust.sh
+
+# It is important that cargo is executed from `src/rust` so that it loads the
+# configuration for the vendored dependencies.
+bitbox03-boot0:
+ (cd src/rust; cargo bitbox03-boot0-stm32u5a9j-dk)
+ arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0
+ arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0
+bitbox03-boot0-release:
+ (cd src/rust; cargo bitbox03-boot0-stm32u5a9j-dk-release)
+ arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot0
+ arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot0
+
+flash-bitbox03-boot0-openocd:
+ ./scripts/flash-bitbox03-boot0-openocd.sh
### scripts/flash-bitbox03-boot0-openocd.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+image="${1:-$repo_root/src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0}"
+config="$repo_root/scripts/stm32u5a9j-dk.cfg"
+
+if [[ ! -f "$image" ]]; then
+ echo "image not found: $image" >&2
+ exit 1
+fi
+
+exec openocd \
+ -f "$config" \
+ -c "program \"$image\" verify reset exit"
### scripts/stm32u5.gdb
@@ -0,0 +1,22 @@
+# Helper function to start RTT server in openocd. Remember to let the firmware
+# run until RTT channels have been initialized before running this command.
+# Sets up channel 0 in/out for terminals and channel 1 for API requests/responses
+define rtt_start
+ monitor rtt setup 0x20000200 0x1000
+ monitor rtt start
+ monitor rtt server start 19021 0
+ monitor rtt server start 19022 1
+end
+
+define r
+ monitor reset halt
+end
+
+target extended-remote :3333
+
+monitor reset init
+
+load
+
+# Step into first instruction
+si
### scripts/stm32u5a9j-dk.cfg
@@ -0,0 +1,16 @@
+source [find interface/stlink.cfg]
+
+transport select swd
+
+reset_config srst_only connect_assert_srst
+#reset_config none
+
+source [find target/stm32u5x.cfg]
+
+cortex_m reset_config sysresetreq
+
+# set working area beyond the RTT buffers so they don't get clobbered
+set _TARGETNAME [target current]
+$_TARGETNAME configure -work-area-phys 0x20002000
+
+adapter speed 3300
### src/CMakeLists.txt
@@ -260,6 +260,7 @@ if(NOT CMAKE_CROSSCOMPILING)
${CARGO} clippy
$<$<BOOL:${CMAKE_VERBOSE_MAKEFILE}>:-v>
--all-features
+ --workspace
--manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/rust/Cargo.toml
--target-dir ${RUST_BINARY_DIR}/clippy
--release
### src/rust/.cargo/config.toml
@@ -21,9 +21,6 @@ replace-with = "vendored-sources"
[source.vendored-sources]
directory = "../../external/vendor"
-[build]
-# Use our own copy of libsecp256k1-zpk instead of the one bundled with secp256k1-sys, also in `cargo
-# test` and other cargo-based builds. This is replicated in src/CMakeLists.txt,
-# test/simulator-graphical*/CMakeLists.txt for CMake-based builds.
-# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
-rustflags = ["--cfg", "rust_secp_no_symbol_renaming"]
+[alias]
+bitbox03-boot0-stm32u5a9j-dk = "build -p bitbox03-boot0 --target=thumbv8m.main-none-eabihf --features board-stm32u5a9j-dk,rtt"
+bitbox03-boot0-stm32u5a9j-dk-release = "build -p bitbox03-boot0 --target=thumbv8m.main-none-eabihf --release --features board-stm32u5a9j-dk"
### src/rust/Cargo.lock
@@ -460,6 +460,18 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "bitbox03-boot0"
+version = "0.1.0"
+dependencies = [
+ "bitbox-board-stm32u5a9j-dk",
+ "bitbox-boot-utils",
+ "bitbox-debug",
+ "bitbox-platform-stm32u5",
+ "cortex-m-rt",
+ "log",
+]
+
[[package]]
name = "bitcoin"
version = "0.32.7"
### src/rust/Cargo.toml
@@ -4,6 +4,7 @@
members = [
"async_test",
+ "bins/bitbox03-boot0",
"bitbox-aes",
"bitbox-board-stm32u5a9j-dk",
"bitbox-board-stm32u5a9j-dk-build",
### src/rust/bins/bitbox03-boot0/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "bitbox03-boot0"
+version = "0.1.0"
+edition = "2024"
+
+[[bin]]
+name = "bitbox03-boot0"
+path = "src/main.rs"
+test = false
+bench = false
+
+[features]
+board-stm32u5a9j-dk = ["dep:bitbox-board-stm32u5a9j-dk"]
+rtt = ["bitbox-debug/rtt"]
+
+[dependencies]
+bitbox-boot-utils = { path = "../../bitbox-boot-utils" }
+bitbox-board-stm32u5a9j-dk = { path = "../../bitbox-board-stm32u5a9j-dk", optional = true }
+bitbox-platform-stm32u5 = { path = "../../bitbox-platform-stm32u5" }
+bitbox-debug = { path = "../../bitbox-debug" }
+cortex-m-rt = "0.7"
+log = { version = "0.4.22", default-features = false }
### src/rust/bins/bitbox03-boot0/bitbox03-boot0.ld
@@ -0,0 +1,6 @@
+INCLUDE memory.x
+REGION_ALIAS("FLASH", BOOT0_FLASH);
+
+PROVIDE(_image_payload_start = ORIGIN(BOOT0_FLASH));
+
+INCLUDE bitbox03-common.ld
### src/rust/bins/bitbox03-boot0/build.rs
@@ -0,0 +1,26 @@
+use std::path::PathBuf;
+
+fn main() {
+ if !cfg!(feature = "board-stm32u5a9j-dk") {
+ panic!("select a BitBox03 board feature, e.g. `board-stm32u5a9j-dk`")
+ }
+
+ let target = std::env::var("TARGET").expect("TARGET not set");
+ if target.starts_with("thumb") {
+ let manifest_dir =
+ PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
+ let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
+
+ let lds_from = manifest_dir.join("bitbox03-boot0.ld");
+ let lds_to = out_dir.join("bitbox03-boot0.ld");
+ println!("cargo::rerun-if-changed={}", lds_from.display());
+ std::fs::copy(lds_from, &lds_to).expect("copy linker script");
+
+ println!("cargo::rustc-link-search={}", out_dir.display());
+ println!(
+ "cargo::rustc-link-arg=-Map={}",
+ out_dir.join("bitbox03-boot0.map").display()
+ );
+ println!("cargo::rustc-link-arg=-Tbitbox03-boot0.ld");
+ }
+}
### src/rust/bins/bitbox03-boot0/src/main.rs
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+#![no_main]
+
+#[cfg(feature = "board-stm32u5a9j-dk")]
+use bitbox_board_stm32u5a9j_dk as board;
+use bitbox_boot_utils::{IMAGE_HEADER_MAGIC_BOOT1, bootload, halt, vector_table_from_image_header};
+use core::panic::PanicInfo;
+use cortex_m_rt::entry;
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ log::error!("{info}");
+ halt();
+}
+
+fn boot1_vector_table() -> Result<*const u32, ()> {
+ // SAFETY: The board memory layout defines BOOT1_ADDR as an aligned,
+ // readable flash slot of BOOT1_MAX_LEN bytes. Boot0 does not mutate it
+ // while validating the image.
+ unsafe {
+ vector_table_from_image_header(
+ board::memory::BOOT1_ADDR,
+ board::memory::BOOT1_MAX_LEN,
+ board::memory::RAM_ADDR,
+ board::memory::RAM_LEN,
+ IMAGE_HEADER_MAGIC_BOOT1,
+ )
+ }
+}
+
+#[entry]
+fn main() -> ! {
+ bitbox_debug::rtt_logger_init!();
+ log::debug!("init");
+
+ let vector_table = match boot1_vector_table() {
+ Ok(vector_table) => vector_table,
+ Err(()) => {
+ log::error!("halt: boot1 image is invalid");
+ halt();
+ }
+ };
+ // SAFETY: Boot0 deliberately trusts boot1 at the fixed slot after checking
+ // its image header and vector table, including the stack pointer and reset
+ // vector ranges.
+ unsafe { bootload(vector_table) }
+}Why this scored 15/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.