fix(core/rust): preserve most significant bit in Obj::small_int
What changed, and why it matters
This commit fixes a low-level bug in how the Trezor firmware converts small integers into an internal MicroPython object representation. Previously, when converting a signed integer, the left-shift operation could discard the top (most significant) bit because it was done on a signed type before being cast to an unsigned type. The fix casts the value to an unsigned type first, preserving all bits. This is a correctness fix in the Rust/MicroPython bridge; it could affect any code path that passes integers to or from MicroPython, but the commit itself does not describe a specific security vulnerability or exploit.
Treat as a routine correctness fix. Review callers of `Obj::small_int` to determine whether any security-sensitive code paths (e.g., cryptographic parameters, path derivations, amount parsing) could have received corrupted integer values on negative or large inputs. Consider requesting a security note from the vendor if this code path is reachable from user input.
Security signals we found
Integer representation correctness fix in firmware/MicroPython bridge
Potential sign-extension/truncation issue in small-int encoding
No explicit security claim or CVE in commit message
No changelog entry provided
Evidence from the diff
In Obj::small_int, the original code ((val << 1) | 1) as usize performed a left shift on a signed integer (val) and then cast to usize. In Rust, shifting a negative signed integer left is defined but the resulting bits are then widened/narrowed via the cast, which can lose the most significant bit of the original value. The patch changes this to let val = val as usize; unsafe { Self::from_bits((val << 1) | 1) }, casting to usize before the shift so the full bit pattern is preserved. Added unit tests verify bit patterns for 0x0000, 0x00f0, and 0xffff.
Changed components
core/embed/rust/src/micropython/obj.rsMicroPython small integer object encodingInspect captured patch +23 / −1
diff --git a/core/embed/rust/src/micropython/obj.rs b/core/embed/rust/src/micropython/obj.rs
index 38e75d00..701e485e 100644
--- a/core/embed/rust/src/micropython/obj.rs
+++ b/core/embed/rust/src/micropython/obj.rs
@@ -114,7 +114,8 @@ impl Obj {
// micropython/py/obj.h
// #define MP_OBJ_NEW_SMALL_INT(small_int) \
// ((mp_obj_t)((((mp_uint_t)(small_int)) << 1) | 1))
- unsafe { Self::from_bits(((val << 1) | 1) as usize) }
+ let val = val as usize;
+ unsafe { Self::from_bits((val << 1) | 1) }
}
}
@@ -473,3 +474,24 @@ impl Obj {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_small_int() {
+ assert_eq!(
+ Obj::small_int(0x0000).as_bits(),
+ 0b00000000000000000000000000000001
+ );
+ assert_eq!(
+ Obj::small_int(0x00f0).as_bits(),
+ 0b00000000000000000000000111100001
+ );
+ assert_eq!(
+ Obj::small_int(0xffff).as_bits(),
+ 0b00000000000000011111111111111111
+ );
+ }
+}
Why this scored 42/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.