What changed, and why it matters
This commit fixes how the Trezor firmware build generates a Rust binding for an internal identifier type called Qstr. Previously, the build always assumed the underlying C enum was a 32-bit integer, but the firmware actually compiles with smaller 16-bit enums. The mismatch could cause the generated Rust code to use the wrong size, leading to memory corruption or crashes when Rust and C exchange Qstr values. The patch makes the build choose the correct enum size for firmware versus host builds.
Treat as a low-to-moderate security hardening fix. Review whether the size mismatch was reachable from untrusted input (e.g., via USB/MicroPython messages) and consider whether a security advisory is warranted if exploitation was feasible. Ensure CI builds both firmware and host variants to catch similar mismatches.
Security signals we found
Memory layout mismatch between C enum and generated Rust binding
Incorrect FFI type size for short-enum firmware builds
Potential out-of-bounds access or corruption in qstr handling
Build-time code generation fix with no runtime changelog entry
Evidence from the diff
The change is in core/embed/rust/build.rs, which uses bindgen to generate Rust bindings for MicroPython’s qstr type. The original code hardcoded c_uint (32-bit) as the expected generated type and then rewrote it to usize. On firmware builds, the C compiler uses -fshort-enums, so the enum backing type is actually c_ushort (16-bit). The patch passes the correct -fshort-enums/-fno-short-enums clang argument to bindgen and updates the string replacement to expect c_ushort for firmware and c_uint otherwise. This prevents a size/layout mismatch between the bindgen-generated struct and the actual C enum, which could lead to out-of-bounds reads/writes or type confusion in the Rust/C interface.
Changed components
core/embed/rust/build.rsMicroPython qstr Rust bindingsTrezor Core firmware buildRust/C FFI layerInspect captured patch +12 / −1
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 9c8aacb5..39479e60 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -112,6 +112,12 @@ fn generate_qstr_bindings() {
let dest_file = PathBuf::from(out_path).join("qstr.rs");
+ let enum_size = if is_firmware() {
+ "-fshort-enums"
+ } else {
+ "-fno-short-enums"
+ };
+
bindgen::Builder::default()
.header("qstr.h")
// Build the Qstr enum as a newtype so we can define method on it.
@@ -121,6 +127,7 @@ fn generate_qstr_bindings() {
})
// Pass in correct include paths.
.clang_args(&["-I", &build_dir()])
+ .clang_arg(enum_size)
// Customize the standard types.
.use_core()
.ctypes_prefix("cty")
@@ -135,10 +142,14 @@ fn generate_qstr_bindings() {
// rewrite the file to change internal representation of the qstr newtype
let qstr_generated = std::fs::read_to_string(&dest_file).unwrap();
+
+ let qstr_enum_type = if is_firmware() { "c_ushort" } else { "c_uint" };
+
let qstr_modified = qstr_generated.replace(
- "pub struct Qstr(pub cty::c_uint);",
+ &format!("pub struct Qstr(pub cty::{});", qstr_enum_type),
"pub struct Qstr(pub usize);",
);
+
assert_ne!(qstr_generated, qstr_modified, "Failed to rewrite type of Qstr in qstr.rs file.\nThis indicates that the generated file has changed. Please update the rewriting code.");
std::fs::write(&dest_file, qstr_modified).unwrap();
}
Why this scored 31/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.