feat(core/rust): coerce empty slices to null FatPtrs
What changed, and why it matters
This commit changes how Trezor firmware passes empty data buffers from Rust code to C code. Previously, an empty Rust slice could produce a pointer that looks like a memory address but points to nothing valid. The patch forces such empty slices to become a standard NULL pointer, which C code is more likely to treat safely. The change is defensive and appears aimed at preventing future bugs or crashes during Rust-to-C communication, rather than fixing an active exploit.
Treat as a defensive hardening commit. Review C call sites that consume FatPtrs to confirm they handle NULL pointers correctly when length is zero, and ensure no code assumes a non-NULL pointer for empty slices. No urgent patch action is indicated unless further analysis shows a specific C consumer was vulnerable to the prior behavior.
Security signals we found
Rust-to-C FFI pointer handling change
Empty slice now coerced to NULL FatPtr
Explicitly motivated by C interop pointer validity checks
Defensive hardening against invalid-looking pointers for zero-length data
No explicit vulnerability, CVE, or exploit described in commit
Evidence from the diff
The patch modifies core/embed/rtl/src/util.rs, specifically the FatPtr<T> wrapper used to pass Rust slices to C. The From<&[T]> implementation now returns FatPtr::null() when the input slice is empty, instead of using s.as_ptr(), which for zero-length slices can be a non-NULL, non-dereferenceable aligned pointer (e.g., align_of<T>). The From<&str> implementation is updated to reuse the byte-slice conversion so empty strings also yield null pointers. Unit tests are added to verify null behavior for empty slices and strings. The commit message frames this as a safety improvement for C interop that validates pointer validity separately from length.
Changed components
core/embed/rtl/src/util.rsFatPtr<T> Rust FFI helperRust-to-C slice/string passing in Trezor Core firmwareInspect captured patch +49 / −6
diff --git a/core/embed/rtl/src/util.rs b/core/embed/rtl/src/util.rs
index e9872d72..6d36de6f 100644
--- a/core/embed/rtl/src/util.rs
+++ b/core/embed/rtl/src/util.rs
@@ -1,6 +1,12 @@
/// Explicit fat pointer representation
///
-/// Useful when passing slices into C
+/// Should be always used when passing slices into C.
+///
+/// Coerces the internal pointer to NULL in case length is zero, to make pointer
+/// validation safer and easier in C. Typically, C will allow either (a) NULL
+/// pointer or (b) pointer to valid memory. But Rust zero-length slices are
+/// pointers whose int value is `align_of<T>`, which is decidedly _not_ valid
+/// memory. This way, C side will be satisfied.
///
/// # Safety
///
@@ -57,9 +63,13 @@ impl<T> FatPtr<T> {
impl<T> From<&[T]> for FatPtr<T> {
fn from(s: &[T]) -> Self {
- Self {
- ptr: s.as_ptr(),
- len: s.len(),
+ if s.is_empty() {
+ Self::null()
+ } else {
+ Self {
+ ptr: s.as_ptr(),
+ len: s.len(),
+ }
}
}
}
@@ -67,9 +77,42 @@ impl<T> From<&[T]> for FatPtr<T> {
// Helper for converting &str to (signed) char*
impl From<&str> for FatPtr<cty::c_char> {
fn from(s: &str) -> Self {
+ let charptr = FatPtr::from(s.as_bytes());
Self {
- ptr: s.as_ptr() as *const cty::c_char,
- len: s.len(),
+ ptr: charptr.ptr() as *const cty::c_char,
+ len: charptr.len(),
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_fat_ptr() {
+ let s = "Hello, world!";
+ let fp = FatPtr::from(s);
+ assert_eq!(fp.ptr() as usize, s.as_ptr() as usize);
+ assert_eq!(fp.len(), s.len());
+ }
+
+ #[test]
+ fn test_nullptr() {
+ let fp = FatPtr::<i32>::null();
+ assert!(fp.is_null());
+ assert_eq!(fp.ptr(), core::ptr::null());
+ assert_eq!(fp.len(), 0);
+ assert!(fp.is_empty());
+ }
+
+ #[test]
+ fn test_empty_slice() {
+ let s: &[u64] = &[];
+ let fp = FatPtr::from(s);
+ assert!(fp.is_null());
+ assert_eq!(fp.ptr(), core::ptr::null());
+ assert_eq!(fp.len(), 0);
+ assert!(fp.is_empty());
+ }
+}
Why this scored 44/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.