bootutilimghash() computes the image digest over ihhdrsize + ihimgsize + ihprotecttlvsize bytes, all three taken from the candidate image's header, and reads that span out of the slot without checking that it fits in the flash area. The three sizes are also summed without an overflow check. Nothing earlier on the validation path bounds the whole span either: bootutilimgvalidate() compares the image size against bootutilmaximagesize() only after the hash has been computed.
The callers bound part of the span, or none of it:
On the swap and overwrite upgrade path, bootvalidateslot() first runs the image header check (bootisheadervalid(), later bootcheckheadervalid()). Before MCUboot 2.2.0 that check covered only ihhdrsize + ihimgsize, so for every image the protected-TLV area (ihprotecttlvsize, a uint16t, up to 65535 bytes) was hashed without being bounded. From 2.2.0 the check includes ihprotecttlvsize, except for a compressed image in the secondary slot when MCUBOOTDECOMPRESSIMAGES (CONFIGBOOTDECOMPRESSION) is enabled, which leaves the same gap of up to 64 KB. The serial recovery image-list and image-state handlers (bslist() and bsset() in boot/bootserial/src/bootserial.c) and Zephyr's single-slot and firmware loaders (boot/zephyr/singleloader.c, boot/zephyr/firmwareloader.c) call bootutilimgvalidate() with no header check at all, so on those paths ihimgsize is unbounded as well.
The image is hashed before its signature is checked, so anyone who can place an image in a slot (an OTA or SMP update delivered through the application, or serial recovery) reaches this read before authentication.
Whether a malformed header produces an actual read beyond the area depends on the flash backend:
Backends whose flasharearead() bounds-checks the request (Zephyr, Espressif, NuttX, Mbed) return an error once the chunked read crosses the end of the area. Validation fails and the image is rejected, with no over-read. With MCUBOOTHASHSTORAGEDIRECTLY (Zephyr CONFIGBOOTIMGHASHDIRECTLYONSTORAGE, available from 2.2.0, not enabled by default) the span is hashed through a single direct pointer into memory-mapped flash, with no bounds check. The Cypress PSoC6 port's flasharearead() (boot/cypress/cyflashpal/cyflashmap.c, from 1.6.0) does not bound the read: its assert()s compare against the area's absolute offset rather than its size, and it copies with memcpy().
The bytes read past the area feed only the digest, which is compared internally and never returned, so nothing is disclosed. The realistic effect is a read fault during image validation: up to 64 KB past the slot on the upgrade path, and potentially much further on the paths that skip the header check. Where validation is repeated at every boot (a pending upgrade, or the single-slot loader checking its image), the fault can recur on each reset and keep the device from booting.
The fix makes bootutilimghash() add the three sizes with bootu32safeadd() and reject an image whose span, including the sector offset used in swap-using-offset mode, does not fit in the flash area, before any of it is hashed. Because the bound is applied where the bytes are read, it covers every caller.
bootutilimghash() computes the image digest over ihhdrsize + ihimgsize + ihprotecttlvsize bytes, all three taken from the candidate image's header, and reads that span out of the slot without checking that it fits in the flash area. The three sizes are also summed without an overflow check. Nothing earlier on the validation path bounds the whole span either: bootutilimgvalidate() compares the image size against bootutilmaximagesize() only after the hash has been computed.
The callers bound part of the span, or none of it:
On the swap and overwrite upgrade path, bootvalidateslot() first runs the image header check (bootisheadervalid(), later bootcheckheadervalid()). Before MCUboot 2.2.0 that check covered only ihhdrsize + ihimgsize, so for every image the protected-TLV area (ihprotecttlvsize, a uint16t, up to 65535 bytes) was hashed without being bounded. From 2.2.0 the check includes ihprotecttlvsize, except for a compressed image in the secondary slot when MCUBOOTDECOMPRESSIMAGES (CONFIGBOOTDECOMPRESSION) is enabled, which leaves the same gap of up to 64 KB. The serial recovery image-list and image-state handlers (bslist() and bsset() in boot/bootserial/src/bootserial.c) and Zephyr's single-slot and firmware loaders (boot/zephyr/singleloader.c, boot/zephyr/firmwareloader.c) call bootutilimgvalidate() with no header check at all, so on those paths ihimgsize is unbounded as well.
The image is hashed before its signature is checked, so anyone who can place an image in a slot (an OTA or SMP update delivered through the application, or serial recovery) reaches this read before authentication.
Whether a malformed header produces an actual read beyond the area depends on the flash backend:
Backends whose flasharearead() bounds-checks the request (Zephyr, Espressif, NuttX, Mbed) return an error once the chunked read crosses the end of the area. Validation fails and the image is rejected, with no over-read. With MCUBOOTHASHSTORAGEDIRECTLY (Zephyr CONFIGBOOTIMGHASHDIRECTLYONSTORAGE, available from 2.2.0, not enabled by default) the span is hashed through a single direct pointer into memory-mapped flash, with no bounds check. The Cypress PSoC6 port's flasharearead() (boot/cypress/cyflashpal/cyflashmap.c, from 1.6.0) does not bound the read: its assert()s compare against the area's absolute offset rather than its size, and it copies with memcpy().
The bytes read past the area feed only the digest, which is compared internally and never returned, so nothing is disclosed. The realistic effect is a read fault during image validation: up to 64 KB past the slot on the upgrade path, and potentially much further on the paths that skip the header check. Where validation is repeated at every boot (a pending upgrade, or the single-slot loader checking its image), the fault can recur on each reset and keep the device from booting.
The fix makes bootutilimghash() add the three sizes with bootu32safeadd() and reject an image whose span, including the sector offset used in swap-using-offset mode, does not fit in the flash area, before any of it is hashed. Because the bound is applied where the bytes are read, it covers every caller.
The MCUboot TLV iterator bootutiltlviternext() in boot/bootutil/src/tlv.c walks image trailer TLVs with a single loop guard (it->tlvoff < it->tlvend) and advances by sizeof(struct imagetlv) + tlv.itlen using the attacker-controlled itlen field, without validating that the entry's header or payload stays within it->tlvend. As a result the 4-byte header read can extend up to 3 bytes past the declared TLV area, and the returned (off, len) can describe a payload that runs past it.
The path is pre-authentication: TLVs are parsed during bootutilimgvalidate() before the signature is verified, and a crafted image can be presented through DFU / serial-recovery / OTA. However the impact is contained by surrounding guards: bootutilimgvalidate() first rejects a TLV area larger than bootutilmaximagesize(), the default LOADIMAGEDATA sink is flasharearead() which bounds-checks against the flash partition and fails an out-of-partition read, and every payload consumer (EXPECTEDHASHTLV, EXPECTEDKEYTLV, EXPECTEDSIGTLV, IMAGETLVSECCNT) clamps len against a fixed buffer before copying.
The residual effect in a default flash build is therefore an out-of-region read confined to the attacker's own slot partition whose result is never observable, yielding at worst a clean validation failure rather than memory corruption or information disclosure. A genuine (though still bounded) out-of-bounds read remains under the non-default MCUBOOTRAMLOAD configuration, where LOADIMAGEDATA is an unchecked memcpy. The fix is to validate each entry's header and payload against it->tlvend inside the iterator.
The MCUboot TLV iterator bootutiltlviternext() in boot/bootutil/src/tlv.c walks image trailer TLVs with a single loop guard (it->tlvoff < it->tlvend) and advances by sizeof(struct imagetlv) + tlv.itlen using the attacker-controlled itlen field, without validating that the entry's header or payload stays within it->tlvend. As a result the 4-byte header read can extend up to 3 bytes past the declared TLV area, and the returned (off, len) can describe a payload that runs past it.
The path is pre-authentication: TLVs are parsed during bootutilimgvalidate() before the signature is verified, and a crafted image can be presented through DFU / serial-recovery / OTA. However the impact is contained by surrounding guards: bootutilimgvalidate() first rejects a TLV area larger than bootutilmaximagesize(), the default LOADIMAGEDATA sink is flasharearead() which bounds-checks against the flash partition and fails an out-of-partition read, and every payload consumer (EXPECTEDHASHTLV, EXPECTEDKEYTLV, EXPECTEDSIGTLV, IMAGETLVSECCNT) clamps len against a fixed buffer before copying.
The residual effect in a default flash build is therefore an out-of-region read confined to the attacker's own slot partition whose result is never observable, yielding at worst a clean validation failure rather than memory corruption or information disclosure. A genuine (though still bounded) out-of-bounds read remains under the non-default MCUBOOTRAMLOAD configuration, where LOADIMAGEDATA is an unchecked memcpy. The fix is to validate each entry's header and payload against it->tlvend inside the iterator.
MCUboot's Zephyr serial-recovery upload handler bsupload() in boot/bootserial/src/bootserial.c decodes the host-supplied image field of an MCUmgr image-upload request into imgnum (a full 32-bit value) and latches it on the off == 0 packet without any range check. It then passes imgnum straight to flashareaidfrommultiimageslot(imgnum, 0), which on the Zephyr port resolves to flashareaidsforslot() in boot/zephyr/include/sysflash/sysflash.h and indexes the static const int allslots[] table as allslots[imgnum 2]. That table holds only MCUBOOTIMAGENUMBER 2 entries, so any imgnum >= MCUBOOTIMAGENUMBER is an out-of-bounds read.
The path is reachable by anyone with access to the serial / SMP recovery console, which is unauthenticated by design, and the bad value is supplied before any image signature is verified. The defect is specific to the default non-direct upload path (MCUBOOTSERIALDIRECTIMAGEUPLOAD disabled) on the Zephyr port with the array-backed slot layout (not CONFIGSINGLEAPPLICATIONSLOT). It is not gated on a multi-image configuration: MCUBOOTIMAGENUMBER defaults to 1, so even a default single-image build is affected for any image >= 1. The MCUBOOTSERIALDIRECTIMAGEUPLOAD path is safe (its flashareaidfromdirectimage() switch returns -EINVAL for unknown indices).
flashareaidfrommultiimageslot() takes a signed int imageindex while imgnum is uint32t, so the attacker controls the sign of the index as well as its magnitude: an image of 0xFFFFFFFE converts to -2 and reads allslots[-4], off the front of the table, while large positive values read off the end (CWE-192). (0xFFFFFFFF itself is the "no image given" sentinel and selects image 0.) Either way the index multiply img 2 is performed in int, so a wild offset yields a bus/hard fault, giving a reliable denial of service of the bootloader during recovery. For moderately out-of-range indices the read returns adjacent .rodata as a garbage flash-area id that is handed to flashareaopen(); this normally fails with MGMTERREINVAL, but if the garbage id happens to match a valid non-image partition, the subsequent erase/write can target a partition outside the image set (CWE-787). The attacker-supplied value is never echoed back, so there is no information disclosure. The fix is to reject imgnum >= BOOTIMAGENUMBER on the off == 0 packet, before the slot lookup. The comparison must be made on the unsigned imgnum: an equivalent guard applied after the conversion to int would not reject the negative case above. flashareaidsforslot() should bounds-check its own arguments as defence in depth. This vulnerability was reported independently twice: by NVIDIA Project Vanessa on 2026-06-22, and by Kai Phan (@kaimandalic) on 2026-07-19 as GHSA-8v33-88p6-jffj, which is closed as a duplicate of this advisory. Both original reports are reproduced in full below.
MCUboot's Zephyr serial-recovery upload handler bsupload() in boot/bootserial/src/bootserial.c decodes the host-supplied image field of an MCUmgr image-upload request into imgnum (a full 32-bit value) and latches it on the off == 0 packet without any range check. It then passes imgnum straight to flashareaidfrommultiimageslot(imgnum, 0), which on the Zephyr port resolves to flashareaidsforslot() in boot/zephyr/include/sysflash/sysflash.h and indexes the static const int allslots[] table as allslots[imgnum 2]. That table holds only MCUBOOTIMAGENUMBER 2 entries, so any imgnum >= MCUBOOTIMAGENUMBER is an out-of-bounds read.
The path is reachable by anyone with access to the serial / SMP recovery console, which is unauthenticated by design, and the bad value is supplied before any image signature is verified. The defect is specific to the default non-direct upload path (MCUBOOTSERIALDIRECTIMAGEUPLOAD disabled) on the Zephyr port with the array-backed slot layout (not CONFIGSINGLEAPPLICATIONSLOT). It is not gated on a multi-image configuration: MCUBOOTIMAGENUMBER defaults to 1, so even a default single-image build is affected for any image >= 1. The MCUBOOTSERIALDIRECTIMAGEUPLOAD path is safe (its flashareaidfromdirectimage() switch returns -EINVAL for unknown indices).
flashareaidfrommultiimageslot() takes a signed int imageindex while imgnum is uint32t, so the attacker controls the sign of the index as well as its magnitude: an image of 0xFFFFFFFF converts to -1 and reads allslots[-2], off the front of the table, while large positive values read off the end (CWE-192). Either way the index multiply img 2 is performed in int, so a wild offset yields a bus/hard fault, giving a reliable denial of service of the bootloader during recovery. For moderately out-of-range indices the read returns adjacent .rodata as a garbage flash-area id that is handed to flashareaopen(); this normally fails with MGMTERREINVAL, but if the garbage id happens to match a valid non-image partition, the subsequent erase/write can target a partition outside the image set (CWE-787). The attacker-supplied value is never echoed back, so there is no information disclosure. The fix is to reject imgnum >= BOOTIMAGENUMBER on the off == 0 packet, before the slot lookup. The comparison must be made on the unsigned imgnum: an equivalent guard applied after the conversion to int would not reject the negative case above. flashareaidsforslot() should bounds-check its own arguments as defence in depth. This vulnerability was reported independently twice: by NVIDIA Project Vanessa on 2026-06-22, and by Kai Phan (@kaimandalic) on 2026-07-19 as GHSA-8v33-88p6-jffj, which is closed as a duplicate of this advisory. Both original reports are reproduced in full below.
MCUboot's serial-recovery image-upload handler bsupload() in boot/bootserial/src/bootserial.c declares const struct flasharea fap = NULL and assigns it only via flashareaopen() part-way through the function. Two early validation failures (goto outinvaliddata) — a CBOR map that fails to decode, or an upload packet missing the off/data members — branch past the open while fap is still NULL, then fall into the shared epilogue. In MCUBOOTENCIMAGES builds that epilogue unconditionally calls boothandleencfw(fap), which reaches bootimageloadheader() and flasharearead() and dereferences the NULL flasharea.
The defect is reachable pre-authentication: the trigger is the very first serial-recovery upload command carrying a malformed payload. The guarding conditions are default-true on a fresh recovery session — imgnum, curroff and imgsize are zero-initialized statics, so curroff == imgsize and the primary-slot test both hold. It requires a non-default build: encrypted images (MCUBOOTENCIMAGES / Zephyr CONFIGBOOTENCRYPTIMAGE) together with an active serial-recovery transport, on the Zephyr or Mynewt port (the two that build both boot/bootserial/src/bootserial.c and bootserialencryption.c with MCUBOOTENCIMAGES).
The impact is a transient denial of service of the bootloader during recovery. fap is a hard NULL rather than an attacker-controlled value, so there is no memory-corruption or information-leak primitive and image authentication is unaffected. Whether the dereference faults is platform-dependent — on targets where address 0x0 is mapped readable flash the read merely returns an error and execution continues, while targets that trap NULL (MPU / null-pointer-exception detection) take a hard fault. Because serial recovery is unauthenticated by design and already permits the console attacker to write the secondary slot, the added capability is only a low-value transient crash.
The related unconditional flashareaclose(fap) call in the same epilogue is a safe no-op on the ports that reach it, so it does not contribute to the dereference.
The affected ports are the Zephyr port — boot/zephyr/CMakeLists.txt compiles bootserial.c under CONFIGMCUBOOTSERIAL and bootserialencryption.c under CONFIGBOOTENCRYPTIMAGE — and the Mynewt port, whose mcubootconfig.h defines MCUBOOTSERIAL unconditionally and MCUBOOTENCIMAGES under any BOOTUTILENCRYPT syscfg, with boot/bootserial/pkg.yml declaring no explicit source list so the whole directory is built. The Espressif port is not affected: it compiles bootserial.c but not bootserialencryption.c, and defines no MCUBOOTENCIMAGES, so the #ifdef MCUBOOTENCIMAGES block containing the call is preprocessed away. The NuttX, Cypress and Mbed ports do not build bootserial.c at all.
splitimagecheck() in boot/bootutil/src/loader.c declares a fixed uint8t loaderhash[32] buffer and passes it as the outhash out-parameter to bootutilimgvalidate(). That routine copies IMAGEHASHSIZE bytes into outhash (memcpy(outhash, hash, IMAGEHASHSIZE) in boot/bootutil/src/imagevalidate.c), where IMAGEHASHSIZE is a compile-time constant of 64 for MCUBOOTSHA512 or 48 for MCUBOOTSIGNEC384/SHA-384 (boot/bootutil/include/bootutil/crypto/sha.h). The buffer tracks SHA-256 (32 bytes) while the copy length tracks the configured algorithm, so the write overruns the buffer by 16 or 32 bytes.
The overflow is only present in a non-default configuration: the legacy Mynewt split-image feature must be compiled (splitgo()/splitimagecheck(), i.e. neither MCUBOOTDIRECTXIP nor MCUBOOTRAMLOAD), the digest must be larger than 32 bytes (MCUBOOTSHA512 or MCUBOOTSIGNEC384), and MCUBOOTSIGNPURE must be off. splitgo() is a public API with no caller inside MCUboot; it is invoked from a Mynewt application. In the default SHA-256 build there is no overflow.
The over-written bytes are the one-way digest of the loader image and the overflow length is fixed at build time, so the contents are not meaningfully attacker-controlled and the size is not attacker-chosen. The realistic impact is bounded, uncontrolled corruption of the bootloader's stack on the split-boot path, manifesting as instability or failed boot in the affected configuration rather than reliable code execution. The fix is to size loaderhash to IMAGEHASHSIZE and pass IMAGEHASHSIZE as the matching seed length.
splitimagecheck() in boot/bootutil/src/loader.c declares a fixed uint8t loaderhash[32] buffer and passes it as the outhash out-parameter to bootutilimgvalidate(). That routine copies IMAGEHASHSIZE bytes into outhash (memcpy(outhash, hash, IMAGEHASHSIZE) in boot/bootutil/src/imagevalidate.c), where IMAGEHASHSIZE is a compile-time constant of 64 for MCUBOOTSHA512 or 48 for MCUBOOTSIGNEC384/SHA-384 (boot/bootutil/include/bootutil/crypto/sha.h). The buffer tracks SHA-256 (32 bytes) while the copy length tracks the configured algorithm, so the write overruns the buffer by 16 or 32 bytes.
The overflow is only present in a non-default configuration: the legacy Mynewt split-image feature must be compiled (splitgo()/splitimagecheck(), i.e. neither MCUBOOTDIRECTXIP nor MCUBOOTRAMLOAD), the digest must be larger than 32 bytes (MCUBOOTSHA512 or MCUBOOTSIGNEC384), and MCUBOOTSIGNPURE must be off. splitgo() is a public API with no caller inside MCUboot; it is invoked from a Mynewt application. In the default SHA-256 build there is no overflow.
The over-written bytes are the one-way digest of the loader image and the overflow length is fixed at build time, so the contents are not meaningfully attacker-controlled and the size is not attacker-chosen. The realistic impact is bounded, uncontrolled corruption of the bootloader's stack on the split-boot path, manifesting as instability or failed boot in the affected configuration rather than reliable code execution. The fix is to size loaderhash to IMAGEHASHSIZE and pass IMAGEHASHSIZE as the matching seed length.
bootsavebootstatus() in boot/bootutil/src/bootrecord.c reads the image's EXPECTEDHASHTLV into a stack buffer imagehash[IMAGEHASHSIZE] that is never zero-initialised. The TLV handler validates only the upper bound (len > sizeof(imagehash)) and then reads exactly len bytes, so a TLV shorter than IMAGEHASHSIZE leaves the tail of imagehash holding stale stack content. The subsequent copy into the boot record, memcpy(buf + offset, imagehash, sizeof(imagehash)), uses the fixed buffer size rather than len, so up to IMAGEHASHSIZE - 1 bytes of uninitialised bootloader stack are written into the measured-boot record.
The defect is reachable only under MCUBOOTMEASUREDBOOT, and only when nothing has checked the length of the hash TLV before bootsavebootstatus() reads it. bootutilimgvalidate() rejects a hash TLV whose length is not exactly sizeof(hash) (boot/bootutil/src/imagevalidate.c:345), and primary-slot validation is on by default (Zephyr's CONFIGBOOTVALIDATESLOT0 defaults to y), so in a default build the malformed image never reaches this code. Two kinds of build let a short TLV through:
Builds that use pure signatures (MCUBOOTSIGNPURE, Zephyr CONFIGBOOTSIGNATURETYPEPURE). The signature covers the image itself, so the hash-TLV check in bootutilimgvalidate() is compiled out, and a pure-signed image normally carries no hash TLV. A hash TLV is still allowed in the unprotected TLV area, which the signature does not cover, so a short one can be added to a correctly signed image without invalidating it. This applies with every upgrade strategy, including direct-XIP and RAM-load, and the modified image can be delivered through any path that accepts a signed image. Builds with primary-slot validation turned off: the swap and overwrite loader built without MCUBOOTVALIDATEPRIMARYSLOT, which checks only the image magic before calling bootaddshareddata(), and the Zephyr single-slot loader (boot/zephyr/singleloader.c) built without it, including the MCUBOOTVALIDATEPRIMARYSLOTONCE variant, which skips validation once the trailer records an earlier successful one. Placing the crafted image requires physical or flash-write access to the device; in a single-slot build with serial recovery enabled, the serial-recovery upload writes the primary slot directly and can place it as well. Outside pure-signature builds, the direct-XIP and RAM-load strategies in boot/bootutil/src/loader.c validate the selected slot unconditionally and are not affected.
It is also reachable through a time-of-check/time-of-use change to tamperable external flash between validation and the boot-record read.
In these configurations the boot record's measurement is already not trustworthy: the hash copied into the record is the image's own EXPECTEDHASHTLV, which nothing has verified and which the attacker chooses. The defect therefore does not weaken attestation integrity any further. What it adds is a path for bootloader memory to leave the device: up to 63 bytes (IMAGEHASHSIZE - 1) of uninitialised bootloader stack are embedded in the measurement field that a downstream attestation service sends to a remote verifier. In the configurations analysed the residue is bootloader internal state (TLV offsets and lengths, flash addresses, loop state), not key material, but what occupies that stack region depends on configuration and on which code ran before bootsavebootstatus(). It is reported as a low-severity, defense-in-depth disclosure with no integrity or availability impact.
This is distinct from the already-fixed recordlen integer underflow in the same routine (GHSA-8hrv-4cp5-4rg3). The fix rejects any EXPECTEDHASHTLV whose length is not exactly IMAGEHASHSIZE.
bootsavebootstatus() in boot/bootutil/src/bootrecord.c reads the image's EXPECTEDHASHTLV into a stack buffer imagehash[IMAGEHASHSIZE] that is never zero-initialised. The TLV handler validates only the upper bound (len > sizeof(imagehash)) and then reads exactly len bytes, so a TLV shorter than IMAGEHASHSIZE leaves the tail of imagehash holding stale stack content. The subsequent copy into the boot record, memcpy(buf + offset, imagehash, sizeof(imagehash)), uses the fixed buffer size rather than len, so up to IMAGEHASHSIZE - 1 bytes of uninitialised bootloader stack are written into the measured-boot record.
The defect is reachable only under MCUBOOTMEASUREDBOOT, and only when the image reaching bootsavebootstatus() was not hash-validated on that boot. bootutilimgvalidate() rejects a hash TLV whose length is not exactly sizeof(hash) (boot/bootutil/src/imagevalidate.c:345), and primary-slot validation is on by default (Zephyr's CONFIGBOOTVALIDATESLOT0 defaults to y), so in a default build the malformed image never reaches this code. The direct-XIP and RAM-load strategies in boot/bootutil/src/loader.c validate the selected slot unconditionally and are not affected. The short TLV survives only where the integrator has turned primary-slot validation off: the swap and overwrite loader built without MCUBOOTVALIDATEPRIMARYSLOT, which checks only the image magic before calling bootaddshareddata(), and the Zephyr single-slot loader (boot/zephyr/singleloader.c) built without it, including the MCUBOOTVALIDATEPRIMARYSLOTONCE variant, which skips validation once the trailer records an earlier successful one. It is also reachable through a time-of-check/time-of-use change to tamperable external flash between validation and the boot-record read. Placing the crafted image requires physical or flash-write access to the device; in a single-slot build with serial recovery enabled, the serial-recovery upload writes the primary slot directly and can place it as well.
In these configurations the boot record's measurement is already not trustworthy: with validation off, the hash copied into the record is the image's own unverified EXPECTEDHASHTLV, which the attacker chooses. The defect therefore does not weaken attestation integrity any further. What it adds is a path for bootloader memory to leave the device: up to 63 bytes (IMAGEHASHSIZE - 1) of uninitialised bootloader stack are embedded in the measurement field that a downstream attestation service sends to a remote verifier. In the configurations analysed the residue is bootloader internal state (TLV offsets and lengths, flash addresses, loop state), not key material, but what occupies that stack region depends on configuration and on which code ran before bootsavebootstatus(). It is reported as a low-severity, defense-in-depth disclosure with no integrity or availability impact.
This is distinct from the already-fixed recordlen integer underflow in the same routine (GHSA-8hrv-4cp5-4rg3). The fix rejects any EXPECTEDHASHTLV whose length is not exactly IMAGEHASHSIZE.
When MCUboot is built with the TinyCrypt crypto backend (MCUBOOTUSETINYCRYPT), bootutilaesctrdrop() in boot/bootutil/include/bootutil/crypto/aesctrtinycrypt.h was an empty stub. The AES-CTR context for this backend is the fully expanded AES-128 key schedule (struct tcaeskeyschedstruct). Generic code relies on this hook to release and clear key state, so with TinyCrypt nothing was cleared. The mbedTLS and PSA backends do free or destroy their key state here.
As a result, bootstateclear(), which bootgo() calls before handing off to the application, left the image's AES content-encryption key schedule in the static bootdata state. This is the case after an encrypted upgrade in single-image builds, where bootenczeroize() is not called. bootdecryptkey() likewise left the ECIES-derived key-encryption key schedule on the stack. The affected build uses MCUBOOTENCIMAGES with TinyCrypt, which is the default ECDSA and Ed25519 backend in the Zephyr port.
The first AES-128 round key equals the cipher key, so anyone who can read the leftover bootloader RAM after boot can recover the image content key and decrypt that encrypted image. That includes a physical attacker with debug or RAM access, or code later running on the device. This weakens image-encryption key confidentiality (and so the confidentiality of the encrypted image's plaintext). The device's long-term private decryption key is not exposed, and image authenticity is not affected. The fix wipes the context with bootutilwipememory().
When MCUboot is built with the TinyCrypt crypto backend (MCUBOOTUSETINYCRYPT), bootutilaesctrdrop() in boot/bootutil/include/bootutil/crypto/aesctrtinycrypt.h was an empty stub. The AES-CTR context for this backend is the fully expanded AES-128 key schedule (struct tcaeskeyschedstruct). Generic code relies on this hook to release and clear key state, so with TinyCrypt nothing was cleared. The mbedTLS and PSA backends do free or destroy their key state here.
As a result, bootstateclear(), which bootgo() calls before handing off to the application, left the image's AES content-encryption key schedule in the static bootdata state. This is the case after an encrypted upgrade in single-image builds, where bootenczeroize() is not called. bootdecryptkey() likewise left the ECIES-derived key-encryption key schedule on the stack. The affected build uses MCUBOOTENCIMAGES with TinyCrypt, which is the default ECDSA and Ed25519 backend in the Zephyr port.
The first AES-128 round key equals the cipher key, so anyone who can read the leftover bootloader RAM after boot can recover the image content key and decrypt that encrypted image. That includes a physical attacker with debug or RAM access, or code later running on the device. This weakens image-encryption key confidentiality (and so the confidentiality of the encrypted image's plaintext). The device's long-term private decryption key is not exposed, and image authenticity is not affected. The fix wipes the context with bootutilwipememory().
In MCUboot's serial-recovery encrypted-image support (boot/bootserial/src/bootserialencryption.c), decryptimageinplace() and bootimagevalidateencrypted() each keep a stack-local struct bootstatus. bootencload() writes the plaintext per-image AES key into its enckey[] field. Neither function cleared this structure before returning. In addition, a decrypt failure inside the per-sector loop of decryptimageinplace() returned early and skipped the totalout: cleanup, which left the expanded AES key context (encdata) on the stack as well. The error paths of bootimagevalidateencrypted() similarly skipped bootenczeroize().
The affected code is built when encrypted images (MCUBOOTENCIMAGES, Zephyr CONFIGBOOTENCRYPTIMAGE) are combined with serial recovery. It runs when an encrypted image is uploaded to the primary slot over the recovery protocol, and when the image list or state is queried while an encrypted image is in the secondary slot. After these functions return, the image's AES content key remains in dead stack memory. An attacker who can later read device RAM, for example through a debug port or retained RAM, can recover it.
This weakens the confidentiality of image-encryption keys within the bootloader trust boundary. A recovered key exposes the plaintext of the encrypted firmware image it protects. The device's long-term private decryption key is not exposed. No remote or recovery-protocol channel that returns this memory is known, so exploitation requires a separate memory-read capability.
In MCUboot's serial-recovery encrypted-image support (boot/bootserial/src/bootserialencryption.c), decryptimageinplace() and bootimagevalidateencrypted() each keep a stack-local struct bootstatus. bootencload() writes the plaintext per-image AES key into its enckey[] field. Neither function cleared this structure before returning. In addition, a decrypt failure inside the per-sector loop of decryptimageinplace() returned early and skipped the totalout: cleanup, which left the expanded AES key context (encdata) on the stack as well. The error paths of bootimagevalidateencrypted() similarly skipped bootenczeroize().
The affected code is built when encrypted images (MCUBOOTENCIMAGES, Zephyr CONFIGBOOTENCRYPTIMAGE) are combined with serial recovery. It runs when an encrypted image is uploaded to the primary slot over the recovery protocol, and when the image list or state is queried while an encrypted image is in the secondary slot. After these functions return, the image's AES content key remains in dead stack memory. An attacker who can later read device RAM, for example through a debug port or retained RAM, can recover it.
This weakens the confidentiality of image-encryption keys within the bootloader trust boundary. A recovered key exposes the plaintext of the encrypted firmware image it protects. The device's long-term private decryption key is not exposed. No remote or recovery-protocol channel that returns this memory is known, so exploitation requires a separate memory-read capability.
MCUboot is a secure bootloader for 32-bits microcontrollers. MCUboot uses a TLV (tag-length-value) structure to represent the meta data associated with an image. The TLVs themselves are divided into two sections, a protected and an unprotected section. The protected TLV entries are included as part of the image signature to avoid tampering. However, the code does not distinguish which TLV entries should be protected or not, so it is possible for an attacker to add unprotected TLV entries that should be protected. Currently, the primary protected TLV entries should be the dependency indication, and the boot record. An injected dependency value would primarily result in an otherwise acceptable image being rejected. A boot record injection could allow fields in a later attestation record to include data not intended, which could cause an image to appear to have properties that it should not have. As a workaround, disable the boot record functionality.