What changed, and why it matters
This commit removes the use of the standard snprintf formatting function from the BitBox02 bootloader's developer menu and replaces it with a small, fixed-format helper. The main goal is to reduce the bootloader's binary size and make the project's automated checks apply to all bootloader builds, not just production ones. There is no direct evidence this fixes an active security vulnerability, but it is a defensive hardening change that reduces attack surface by avoiding a complex formatting library in security-sensitive bootloader code.
Treat as a hardening improvement. Reviewers should verify that bootloader_format_ble_firmware_version() correctly handles all edge cases (e.g., minimum out_len, null hash pointer, version values) and that the CI symbol check now catches unwanted formatting symbols in development bootloader builds. No urgent security response is indicated by the available evidence.
Security signals we found
Removes snprintf/printf usage from bootloader code, reducing binary bloat and eliminating a complex formatting library from a privileged execution context
Extends automated CI check to detect stdio/Rust formatting symbols in regular development and production bootloader outputs, not just production builds
Adds unit test coverage for the new fixed-format helper
No explicit vulnerability, CVE, or security advisory is mentioned in the commit or supplied references
Evidence from the diff
The change replaces a call to snprintf() in bootloader.c’s BLE firmware version display with a new fixed-format helper, bootloader_format_ble_firmware_version(), implemented in bootloader_format.c. The helper uses rust_format_uint() and util_uint8_to_hex() to build the string without pulling in stdio formatting code. The CI check for unwanted symbols is broadened from production-only bootloader ELFs to all bootloader ELFs except debug builds, and its messages are generalized. Unit tests are added for the new formatter. The commit explicitly states its purpose is size reduction and extending the symbol check.
Changed components
src/bootloader/bootloader.csrc/bootloader/bootloader_format.csrc/bootloader/bootloader_format.h.ci/check-unwanted-symbols.github/workflows/ci-common.ymltest/unit-test/test_bootloader_format.cInspect captured patch +59 / −10
diff --git a/.ci/check-unwanted-symbols b/.ci/check-unwanted-symbols
index 9af9072..7a1650b 100755
--- a/.ci/check-unwanted-symbols
+++ b/.ci/check-unwanted-symbols
@@ -63,7 +63,7 @@ check_firmware() {
"Use integer arithmetic in firmware code instead."
}
-check_production_bootloader() {
+check_bootloader() {
local elf=$1
checked=1
@@ -74,7 +74,7 @@ check_production_bootloader() {
"stdio formatting" \
"$printf_symbol_pattern" \
"snprintf/printf pulls in significant bootloader bloat." \
- "Avoid stdio formatting in production bootloaders; use fixed-format helpers instead."
+ "Avoid stdio formatting in bootloaders; use fixed-format helpers instead."
local rust_fmt_symbol_pattern
rust_fmt_symbol_pattern='(^|[[:space:]])(alloc::fmt::|core::fmt::|<alloc::string::String as core::fmt::Write>)'
@@ -83,14 +83,14 @@ check_production_bootloader() {
"Rust formatting" \
"$rust_fmt_symbol_pattern" \
"Rust format!/write! formatting pulls in significant bootloader bloat." \
- "Avoid Rust formatting in production bootloaders; use fixed-format helpers instead."
+ "Avoid Rust formatting in bootloaders; use fixed-format helpers instead."
check_symbols \
"$elf" \
"Rust SHA-256" \
"sha26sha256|sha2::sha256::compress256" \
- "The RustCrypto SHA-256 implementation pulls in significant production bootloader bloat." \
- "Use the existing PUKCC SHA-256 implementation in production bootloaders instead."
+ "The RustCrypto SHA-256 implementation pulls in significant bootloader bloat." \
+ "Use the existing PUKCC SHA-256 implementation in bootloaders instead."
}
firmware_elf=build/bin/firmware.elf
@@ -99,8 +99,8 @@ if [[ -f "$firmware_elf" ]]; then
fi
shopt -s nullglob
-for bootloader_elf in build/bin/*-bl-*-production.elf; do
- check_production_bootloader "$bootloader_elf"
+for bootloader_elf in build/bin/bb02-bl-*.elf build/bin/bb02p-bl-*.elf; do
+ check_bootloader "$bootloader_elf"
done
shopt -u nullglob
diff --git a/.github/workflows/ci-common.yml b/.github/workflows/ci-common.yml
index 8663c95..1ed4c6a 100644
--- a/.github/workflows/ci-common.yml
+++ b/.github/workflows/ci-common.yml
@@ -242,7 +242,7 @@ jobs:
run: make -j$(($(nproc)+1)) ${{ matrix.target }}
- name: Check unwanted symbols
- if: (matrix.target == 'firmware' || (startsWith(matrix.target, 'bootloader') && endsWith(matrix.target, 'production'))) && !cancelled()
+ if: (matrix.target == 'firmware' || (startsWith(matrix.target, 'bootloader') && !endsWith(matrix.target, 'debug'))) && !cancelled()
run: ./.ci/check-unwanted-symbols
- name: Print hashes
diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c
index 3ebbd7b..569f647 100644
--- a/src/bootloader/bootloader.c
+++ b/src/bootloader/bootloader.c
@@ -1007,8 +1007,8 @@ static bool _devdevice_enter(secbool_u32 firmware_verified)
struct da14531_firmware_version version;
bool res = memory_spi_get_active_ble_firmware_version(&version);
if (res) {
- char buf[50];
- snprintf(buf, sizeof(buf), "ble: %d (%s)", version.version, util_dbg_hex(version.hash, 4));
+ char buf[sizeof("ble: 65535 (00112233)")];
+ bootloader_format_ble_firmware_version(buf, sizeof(buf), version.version, version.hash);
UG_PutString(0, SCREEN_HEIGHT - 18, buf);
}
#endif
diff --git a/src/bootloader/bootloader_format.c b/src/bootloader/bootloader_format.c
index 5e407b7..e78f8a4 100644
--- a/src/bootloader/bootloader_format.c
+++ b/src/bootloader/bootloader_format.c
@@ -4,6 +4,7 @@
#include <rust/rust.h>
#include <string.h>
+#include <util.h>
#include <utils_assert.h>
void bootloader_format_pairing_code(char* out, size_t out_len, uint32_t pairing_code)
@@ -40,6 +41,25 @@ void bootloader_format_timer(char* out, size_t out_len, uint8_t seconds)
out[out_pos] = '\0';
}
+void bootloader_format_ble_firmware_version(
+ char* out,
+ size_t out_len,
+ uint16_t version,
+ const uint8_t* hash)
+{
+ ASSERT(out_len >= sizeof("ble: 65535 (00112233)"));
+
+ util_strlcpy(out, "ble: ", out_len);
+ size_t out_pos = strlen(out);
+ out_pos += rust_format_uint(
+ rust_util_bytes_mut((uint8_t*)&out[out_pos], out_len - out_pos), version, 1, '0');
+ util_strlcpy(&out[out_pos], " (", out_len - out_pos);
+ out_pos = strlen(out);
+ util_uint8_to_hex(hash, 4, &out[out_pos]);
+ out_pos += 8;
+ util_strlcpy(&out[out_pos], ")", out_len - out_pos);
+}
+
void bootloader_format_unknown_command(char* out, size_t out_len, uint8_t command)
{
const char prefix[] = "Command: ";
diff --git a/src/bootloader/bootloader_format.h b/src/bootloader/bootloader_format.h
index 048b120..4dbd6ac 100644
--- a/src/bootloader/bootloader_format.h
+++ b/src/bootloader/bootloader_format.h
@@ -35,6 +35,18 @@ void bootloader_format_hash_multiline(char* out, size_t out_len, const char* has
*/
void bootloader_format_timer(char* out, size_t out_len, uint8_t seconds);
+/**
+ * Format the BLE firmware version line shown in the dev bootloader menu.
+ *
+ * The first four hash bytes are printed. out_len must be at least
+ * sizeof("ble: 65535 (00112233)").
+ */
+void bootloader_format_ble_firmware_version(
+ char* out,
+ size_t out_len,
+ uint16_t version,
+ const uint8_t* hash);
+
/**
* Format an unknown bootloader command message.
*
diff --git a/test/unit-test/test_bootloader_format.c b/test/unit-test/test_bootloader_format.c
index 0370c55..702bfc1 100644
--- a/test/unit-test/test_bootloader_format.c
+++ b/test/unit-test/test_bootloader_format.c
@@ -64,6 +64,22 @@ static void test_timer(void** state)
assert_string_equal(out, "10s");
}
+static void test_ble_firmware_version(void** state)
+{
+ (void)state;
+ char out[sizeof("ble: 65535 (00112233)")];
+ const uint8_t hash[] = {
+ 0x00,
+ 0x11,
+ 0x22,
+ 0x33,
+ 0x44,
+ };
+
+ bootloader_format_ble_firmware_version(out, sizeof(out), 65535, hash);
+ assert_string_equal(out, "ble: 65535 (00112233)");
+}
+
static void test_unknown_command(void** state)
{
(void)state;
@@ -80,6 +96,7 @@ int main(void)
cmocka_unit_test(test_progress),
cmocka_unit_test(test_hash_multiline),
cmocka_unit_test(test_timer),
+ cmocka_unit_test(test_ble_firmware_version),
cmocka_unit_test(test_unknown_command),
};
return cmocka_run_group_tests(tests, NULL, NULL);
Why this scored 18/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.