refactor(core): update users of micropython uzlib
What changed, and why it matters
This commit updates Trezor firmware code to match a renamed and slightly changed version of the micropython uzlib decompression library. It renames types (e.g., `uzlib_uncomp` to `uzlib_uncomp_t`), replaces old return-code constants (`TINF_OK` with `UZLIB_OK`), and adds a new `source_read_data` field used by the library's callback mechanism. The changes are a routine refactor to stay compatible with an upstream micropython update. There is no direct evidence in the commit that this fixes a security vulnerability, but any mismatch between firmware and its decompression library could in principle cause crashes or incorrect behavior during firmware updates.
Treat as a maintenance refactor. Verify that the new `source_read_data` pointer is always correctly set and cleared, and that the callback signature change does not introduce use-after-free or null-dereference paths during firmware update decompression. Review the upstream micropython commits for any associated security fixes, but do not assume this commit alone resolves a vulnerability.
Security signals we found
Refactor triggered by upstream library API change
Callback context pointer handling changed (source_read_cb_context and source_read_data)
Bootloader decompression code touched
No changelog entry, reducing audit trail
No explicit security wording in commit or supplied references
Evidence from the diff
The patch adapts Trezor’s Rust and C uzlib consumers to micropython changes that renamed tinf symbols to uzlib and introduced a source_read_data argument passed to source_read_cb. In build.rs the bindgen allowlist adds uzlib_uncomp_t. In uzlib.rs the old UzlibContext helper is removed, the struct type is updated throughout, and source_read_data is set to &self.uncomp before decompression and cleared afterward. The C bootloader code in boot_image.c updates struct names and return-code checks. The commit message frames this as a refactor needed because of upstream micropython changes, with a ‘trezor-specific patch’ and ‘[no changelog]’.
Changed components
core/embed/rust/build.rscore/embed/rust/src/trezorhal/uzlib.rscore/embed/sec/image/stm32/boot_image.cmicropython uzlib integrationbootloader image decompressionInspect captured patch +18 / −62
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 05c26dd4..57f1da49 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -226,6 +226,7 @@ fn generate_trezorhal_bindings(lib: &mut CLibrary) -> Result<()> {
.allowlist_function("gfx_mono8_blend_mono4")
.allowlist_function("gfx_bitblt_wait")
// uzlib
+ .allowlist_type("uzlib_uncomp_t")
.allowlist_function("uzlib_uncompress_init")
.allowlist_function("uzlib_uncompress")
// bip39
diff --git a/core/embed/rust/src/trezorhal/uzlib.rs b/core/embed/rust/src/trezorhal/uzlib.rs
index 89770c9e..0f0ebe85 100644
--- a/core/embed/rust/src/trezorhal/uzlib.rs
+++ b/core/embed/rust/src/trezorhal/uzlib.rs
@@ -1,67 +1,19 @@
use core::cell::RefCell;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
-use core::ptr;
use super::ffi;
use crate::io::BinaryData;
pub const UZLIB_WINDOW_SIZE: usize = 1 << 10;
-pub use ffi::uzlib_uncomp;
+pub use ffi::uzlib_uncomp_t;
-impl Default for ffi::uzlib_uncomp {
+impl Default for ffi::uzlib_uncomp_t {
fn default() -> Self {
unsafe { MaybeUninit::<Self>::zeroed().assume_init() }
}
}
-pub struct UzlibContext<'a> {
- uncomp: ffi::uzlib_uncomp,
- src_data: PhantomData<&'a [u8]>,
-}
-
-impl<'a> UzlibContext<'a> {
- pub fn new(src: &'a [u8], window: Option<&'a mut [u8; UZLIB_WINDOW_SIZE]>) -> Self {
- let mut ctx = Self {
- uncomp: uzlib_uncomp::default(),
- src_data: PhantomData,
- };
-
- unsafe {
- ctx.uncomp.source = src.as_ptr();
- ctx.uncomp.source_limit = src.as_ptr().add(src.len());
-
- if let Some(w) = window {
- ffi::uzlib_uncompress_init(
- &mut ctx.uncomp,
- w.as_mut_ptr() as _,
- UZLIB_WINDOW_SIZE as u32,
- );
- } else {
- ffi::uzlib_uncompress_init(&mut ctx.uncomp, ptr::null_mut(), 0);
- }
- }
-
- ctx
- }
-
- /// Returns `Ok(true)` if all data was read.
- pub fn uncompress(&mut self, dest_buf: &mut [u8]) -> Result<bool, ()> {
- unsafe {
- self.uncomp.dest = dest_buf.as_mut_ptr();
- self.uncomp.dest_limit = self.uncomp.dest.add(dest_buf.len());
-
- let res = ffi::uzlib_uncompress(&mut self.uncomp);
-
- match res {
- 0 => Ok(false),
- 1 => Ok(true),
- _ => Err(()),
- }
- }
- }
-}
-
struct SourceReadContext<'a> {
/// Compressed data
data: BinaryData<'a>,
@@ -86,7 +38,7 @@ impl<'a> SourceReadContext<'a> {
}
/// Fill the uncomp struct with the appropriate pointers to the source data
- pub fn prepare_uncomp(&self, uncomp: &mut ffi::uzlib_uncomp) {
+ pub fn prepare_uncomp(&self, uncomp: &mut ffi::uzlib_uncomp_t) {
// SAFETY: the offsets are within the buffer bounds.
// - buf_head is either 0 or advanced by uzlib to at most buf_tail (via
// advance())
@@ -104,7 +56,7 @@ impl<'a> SourceReadContext<'a> {
/// The operation is only valid on an uncomp struct that has been filled via
/// `prepare_uncomp` and the source pointer has been updated by the uzlib
/// library after a single uncompress operation.
- pub unsafe fn advance(&mut self, uncomp: &ffi::uzlib_uncomp) {
+ pub unsafe fn advance(&mut self, uncomp: &ffi::uzlib_uncomp_t) {
unsafe {
// SAFETY: we trust uzlib to move the `source` pointer only up to `source_limit`
self.buf_head = uncomp.source.offset_from(self.buf.as_ptr()) as usize;
@@ -116,7 +68,7 @@ impl<'a> SourceReadContext<'a> {
/// If the uncomp buffer is exhausted, a callback is invoked that should (a)
/// read one byte of data, and optionally (b) update the uncomp buffer
/// with more data.
- pub fn reader_callback(&mut self, uncomp: &mut ffi::uzlib_uncomp) -> Option<u8> {
+ pub fn reader_callback(&mut self, uncomp: &mut ffi::uzlib_uncomp_t) -> Option<u8> {
// fill the internal buffer first
let bytes_read = self.data.read(self.offset, self.buf.as_mut());
self.buf_head = 0;
@@ -142,7 +94,7 @@ pub struct ZlibInflate<'a> {
/// Compressed data reader
data: RefCell<SourceReadContext<'a>>,
/// Uzlib context
- uncomp: ffi::uzlib_uncomp,
+ uncomp: ffi::uzlib_uncomp_t,
window: PhantomData<&'a [u8]>,
}
@@ -158,7 +110,7 @@ impl<'a> ZlibInflate<'a> {
) -> Self {
let mut inflate = Self {
data: RefCell::new(SourceReadContext::new(data, offset)),
- uncomp: uzlib_uncomp::default(),
+ uncomp: uzlib_uncomp_t::default(),
window: PhantomData,
};
@@ -185,6 +137,7 @@ impl<'a> ZlibInflate<'a> {
self.uncomp.source_read_cb = Some(zlib_reader_callback);
// Context for the source data callback
self.uncomp.source_read_cb_context = &self.data as *const _ as *mut cty::c_void;
+ self.uncomp.source_read_data = &self.uncomp as *const _ as *mut cty::c_void;
// Destination buffer
self.uncomp.dest = dest.as_mut_ptr();
@@ -205,6 +158,7 @@ impl<'a> ZlibInflate<'a> {
// Clear the source read callback (just for safety)
self.uncomp.source_read_cb_context = core::ptr::null_mut();
+ self.uncomp.source_read_data = core::ptr::null_mut();
match res {
0 => Ok(false),
@@ -230,7 +184,8 @@ impl<'a> ZlibInflate<'a> {
/// This function is called by the uzlib library to read more data from the
/// input stream.
-unsafe extern "C" fn zlib_reader_callback(uncomp: *mut ffi::uzlib_uncomp) -> i32 {
+unsafe extern "C" fn zlib_reader_callback(arg: *mut cty::c_void) -> i32 {
+ let uncomp = arg as *mut ffi::uzlib_uncomp_t;
// SAFETY: we assume that passed-in uncomp is not null and that we own it
// exclusively (ensured by passing it as &mut into uzlib_uncompress())
let uncomp = unwrap!(unsafe { uncomp.as_mut() });
diff --git a/core/embed/sec/image/stm32/boot_image.c b/core/embed/sec/image/stm32/boot_image.c
index b3e100f1..5c530dbd 100644
--- a/core/embed/sec/image/stm32/boot_image.c
+++ b/core/embed/sec/image/stm32/boot_image.c
@@ -56,10 +56,10 @@ _Static_assert(
BOOTLOADER_MAXSIZE <= IMAGE_CHUNK_SIZE,
"BOOTLOADER_MAXSIZE must be less than or equal to IMAGE_CHUNK_SIZE");
-static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,
+static void uzlib_prepare(uzlib_uncomp_t *decomp, uint8_t *window,
const void *src, uint32_t srcsize, void *dest,
uint32_t destsize) {
- memzero(decomp, sizeof(struct uzlib_uncomp));
+ memzero(decomp, sizeof(uzlib_uncomp_t));
if (window) {
memzero(window, UZLIB_WINDOW_SIZE);
}
@@ -100,14 +100,14 @@ void boot_image_replace(const boot_image_t *image) {
mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
- struct uzlib_uncomp decomp = {0};
+ uzlib_uncomp_t decomp = {0};
uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0};
uint32_t decomp_out[IMAGE_HEADER_SIZE / sizeof(uint32_t)] = {0};
uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
decomp_out, sizeof(decomp_out));
- ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
+ ensure((uzlib_uncompress(&decomp) == UZLIB_OK) ? sectrue : secfalse,
"Bootloader header decompression failed");
const image_header *new_bld_hdr = read_image_header(
@@ -154,7 +154,7 @@ void boot_image_replace(const boot_image_t *image) {
error_shutdown("Invalid bootloader contents");
}
- memset(&decomp, 0, sizeof(struct uzlib_uncomp));
+ memset(&decomp, 0, sizeof(uzlib_uncomp_t));
// cannot find valid header for current bootloader, something is wrong
ensure(current_bld_hdr == (const image_header *)bl_data ? sectrue : secfalse,
@@ -193,7 +193,7 @@ void boot_image_replace(const boot_image_t *image) {
uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
decomp_out, sizeof(decomp_out));
- ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
+ ensure((uzlib_uncompress(&decomp) == UZLIB_OK) ? sectrue : secfalse,
"Bootloader decompression failed");
do {
Why this scored 29/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.