What changed, and why it matters
This commit changes the build system to enable Link-Time Optimization (LTO), a compiler technique that shrinks the final firmware size. It also adds safeguards to keep the stack protector security feature working under LTO and deliberately excludes sensitive low-level code (bootloader, factory setup, hardware drivers) from LTO to avoid subtle bugs. There is no direct evidence in the commit of a security vulnerability being fixed or introduced.
Treat as a normal build optimization/hardening change. Review that CI reproducibly builds valid firmware images and that stack-protector functionality is verified by tests or binary inspection. No immediate security response is indicated by the commit content alone.
Security signals we found
Build-system change enabling LTO with explicit stack-protector symbol retention
Deliberate exclusion of bootloader, factory-setup, ASF4, samd51a-ds, and embedded-swd from LTO due to audit complexity
Toolchain switch to LTO-aware archive utilities to avoid invalid tiny images from missing live objects
Compiler guard requiring GCC because Clang lacks externally_visible support for stack protector symbols
Evidence from the diff
The patch enables scoped GCC LTO for firmware C code and the optiga/cryptoauthlib static libraries to reduce ROM size. It switches the ARM toolchain to LTO-aware ar/nm/ranlib wrappers, disables LTO for bootloaders and factory-setup firmware, and marks __stack_chk_fail and __stack_chk_guard with used/externally_visible so the stack protector remains functional after LTO. The change is a build-system hardening/size optimization, not a runtime security bug fix.
Changed components
BitBox02 firmware build system (CMake)arm.cmake toolchain configurationexternal/CMakeLists.txt (cryptoauthlib, optiga libraries)src/CMakeLists.txt (firmware and bootloader link rules)src/common_main.c (__stack_chk_fail definition)src/firmware.c (__stack_chk_guard definition)Inspect captured patch +29 / −3
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e624e4..434a34d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,6 +41,15 @@ project(bitbox02 C)
# nosys is set in arm.cmake so that `project(c)` above works. Remove it since it interferes with compile options
if(CMAKE_CROSSCOMPILING)
+ if(NOT CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ message(FATAL_ERROR
+ "Firmware C LTO currently requires GCC. The stack-protector symbols use "
+ "GCC's externally_visible attribute so GCC LTO does not internalize or "
+ "drop __stack_chk_fail/__stack_chk_guard. Clang does not support that "
+ "attribute; add and verify compiler-specific symbol-retention handling "
+ "before enabling firmware C LTO with a non-GNU compiler."
+ )
+ endif()
string(REPLACE "--specs=nosys.specs" "" CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS})
endif()
diff --git a/arm.cmake b/arm.cmake
index 6301002..b5a85e9 100644
--- a/arm.cmake
+++ b/arm.cmake
@@ -2,6 +2,9 @@ set(CMAKE_SYSTEM_NAME "Generic")
set(CMAKE_SYSTEM_PROCESSOR "arm")
set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
+set(CMAKE_AR "arm-none-eabi-gcc-ar")
+set(CMAKE_NM "arm-none-eabi-gcc-nm")
+set(CMAKE_RANLIB "arm-none-eabi-gcc-ranlib")
# Search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 948bb15..178d201 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -176,6 +176,7 @@ set_property(TARGET asf4-drivers PROPERTY INTERFACE_LINK_LIBRARIES "")
${CMAKE_CURRENT_SOURCE_DIR} # for the BitBox02-custom "atca_config.h"
)
target_compile_options(cryptoauthlib PRIVATE
+ -flto -ffat-lto-objects
-Wno-pedantic -Wno-incompatible-pointer-types -Wno-unused-parameter -Wno-unused-variable -Wno-cast-qual
-Wno-switch-default -Wno-format-nonliteral -Wno-missing-prototypes -Wno-missing-declarations
)
@@ -218,7 +219,7 @@ add_library(optiga EXCLUDE_FROM_ALL
)
target_compile_definitions(optiga PRIVATE MBEDTLS_USER_CONFIG_FILE="mbedtls_config.h")
# Ignore warnings in external lib.
-target_compile_options(optiga PRIVATE "-w")
+target_compile_options(optiga PRIVATE "-w" -flto -ffat-lto-objects)
target_compile_definitions(optiga PRIVATE OPTIGA_LIB_EXTERNAL="optiga_config.h")
target_include_directories(optiga SYSTEM PUBLIC
optiga-trust-m/config
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d4ca4eb..9f2867e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -416,6 +416,7 @@ if(CMAKE_CROSSCOMPILING)
foreach(bootloader ${BOOTLOADERS})
set(elf ${bootloader}.elf)
add_executable(${elf} ${BOOTLOADER-SOURCES} ${PLATFORM-BITBOX02-SOURCES})
+ target_compile_options(${elf} PRIVATE -fno-lto)
target_link_libraries(${elf} PRIVATE c asf4-drivers-min samd51a-ds -Wl,-u,exception_table)
target_include_directories(${elf} PRIVATE ${INCLUDES})
target_compile_definitions(${elf} PRIVATE BOOTLOADER "APP_U2F=0")
@@ -483,6 +484,14 @@ if(CMAKE_CROSSCOMPILING)
foreach(firmware ${FIRMWARES})
set(elf ${firmware}.elf)
add_executable(${elf} ${FIRMWARE-SOURCES})
+ # Static libraries are LTO-capable for firmware size, but only firmware images do the LTO link.
+ if(firmware STREQUAL "factory-setup")
+ target_compile_options(${elf} PRIVATE -fno-lto)
+ target_link_libraries(${elf} PRIVATE -fno-lto)
+ else()
+ target_compile_options(${elf} PRIVATE -flto -ffat-lto-objects)
+ target_link_libraries(${elf} PRIVATE -flto)
+ endif()
# Must manually link against C so that malloc can find _sbrk
target_link_libraries(${elf}
PRIVATE
diff --git a/src/common_main.c b/src/common_main.c
index a1d6f0e..a154492 100644
--- a/src/common_main.c
+++ b/src/common_main.c
@@ -14,7 +14,9 @@
#include <rust/rust.h>
extern void __attribute__((noreturn)) __stack_chk_fail(void);
-void __attribute__((noreturn)) __stack_chk_fail(void)
+// GCC LTO needs externally_visible; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void __attribute__((noreturn, used, externally_visible)) __stack_chk_fail(void)
{
Abort("Stack smashing detected");
while (1) {
diff --git a/src/firmware.c b/src/firmware.c
index 928f6af..3d251d0 100644
--- a/src/firmware.c
+++ b/src/firmware.c
@@ -21,7 +21,9 @@
#include <u2f.h>
#endif
-uint32_t __stack_chk_guard = 0;
+// GCC LTO needs externally_visible; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+uint32_t __attribute__((used, externally_visible)) __stack_chk_guard = 0;
int main(void)
{
Why this scored 12/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.