Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

espflash save-image does not update the App Image SHA256 in the correct location. #715

Open
chris-subtlebytes opened this issue Dec 29, 2024 · 1 comment · May be fixed by #716
Open

espflash save-image does not update the App Image SHA256 in the correct location. #715

chris-subtlebytes opened this issue Dec 29, 2024 · 1 comment · May be fixed by #716
Milestone

Comments

@chris-subtlebytes
Copy link

When generating a project with secure boot v2, the bootloader is padded: https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32c3/security/secure-boot-v2.html#secure-padding

Using a padded image with espflash save-image --bootloader path-to-bootloader ... will read the file into a buffer:

flasher/mod.rs:

impl FlashData {
    pub fn new(...) -> Result<Self, Error> {
        // If the '--bootloader' option is provided, load the binary file at the
        // specified path.
        let bootloader = if let Some(path) = bootloader {
            let data = fs::canonicalize(path)
                .and_then(fs::read)
                .map_err(|e| Error::FileOpenError(path.display().to_string(), e))?;

            Some(data)
        } else {
            None
        };

Then that bootloader buffer gets passed to image_format.rs IdfBootloaderFormat. From there, the header is updated to match the args so the sha256 needs updating. However, the location of the sha256 is naively calculated as being the last 32 bytes of the bootloader file. However, it's actually the last 32 bytes of the bootloader without padding.

image_format.rs:

        // re-calculate hash of the bootloader - needed since we modified the header
        let bootloader_len = bootloader.len(); // <- size of the file including padding
        let mut hasher = Sha256::new();
        hasher.update(&bootloader[..bootloader_len - 32]); //  <-- wrong location of sha256
        let hash = hasher.finalize();
        bootloader.to_mut()[bootloader_len - 32..].copy_from_slice(&hash);

One approach, which I'm not sure is the correct one due to all the different environments but worked in my specific case, was to walk through the bootloader segments while adding up the length. Then adding width the 16-byte aligned 1-byte checksum at the end. Maybe a better approach can be derived by referencing esptool/bin_image.py directly.

https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32c3/api-reference/system/app_image_format.html#application-image-structures

The image has a single checksum byte after the last segment. This byte is written on a sixteen byte padded boundary, so the application image might need padding.

Without this, secure-boot-enabled ESPs will not boot because the first-stage bootloader will detect the mismatch between the actual and old unchanged SHA256.

This appears to be related but not the same as #713

chris-subtlebytes pushed a commit to chris-subtlebytes/espflash that referenced this issue Dec 29, 2024
The current implementation uses the last 32 bytes of the bootloader
file. When Secure Boot V2 is enabled, the bootloader is padded. The
new implementation walks through the segments to find the end and adds
the 16-byte aligned 1-byte checksum to update the SHA256 instead of
incorrectly updating the padding.

Closes esp-rs#715
@chris-subtlebytes chris-subtlebytes linked a pull request Dec 29, 2024 that will close this issue
chris-subtlebytes pushed a commit to chris-subtlebytes/espflash that referenced this issue Dec 30, 2024
The current implementation uses the last 32 bytes of the bootloader
file. When Secure Boot V2 is enabled, the bootloader is padded. The
new implementation walks through the segments to find the end and adds
the 16-byte aligned 1-byte checksum to update the SHA256 instead of
incorrectly updating the padding.

Closes esp-rs#715
@chris-subtlebytes
Copy link
Author

more details:

$ python -m esptool image_info --version 2 image.bin
esptool.py v4.8.1
File size: 45056 (bytes)
Detected image type: ESP32-C3

ESP32-C3 image header
=====================
Image version: 1
Entry point: 0x403cc710
Segments: 3
Flash size: 4MB
Flash freq: 40m
Flash mode: DIO

ESP32-C3 extended image header
==============================
WP pin: 0xee (disabled)
Flash pins drive settings: clk_drv: 0x0, q_drv: 0x0, d_drv: 0x0, cs0_drv: 0x0, hd_drv: 0x0, wp_drv: 0x0
Chip ID: 5 (ESP32-C3)
Minimal chip revision: v0.3, (legacy min_rev = 3)
Maximal chip revision: v1.99

Segments information
====================
Segment   Length   Load addr   File offs  Memory types
-------  -------  ----------  ----------  ------------
      0  0x03a84  0x3fcd5988  0x00000018  DRAM, BYTE_ACCESSIBLE
      1  0x00968  0x403cc710  0x00003aa4  IRAM
      2  0x05c00  0x403ce710  0x00004414  IRAM

ESP32-C3 image footer
=====================
Checksum: 0xb8 (valid)
Validation hash: a3e7ee9e006567278ecb31b65bfb2927138879a4c9b3b693d0f36815f2acf6be (valid)

snippet from xxd for a valid image file:

...
00009ff0: aa85 3766 cd3f 3795 cd3f 1306 86d8 1305  ..7f.?7..?......
0000a000: 4519 97c0 c2ff e780 a0d4 0565 1305 4560  E..........e..E`
0000a010: 45b7 8509 c9b7 9107 45bf 0000 0000 00b8  E.......E....... # <-- last byte of 16-byte aligned is 0xb8, the checksum
0000a020: a3e7 ee9e 0065 6727 8ecb 31b6 5bfb 2927  .....eg'..1.[.)' # <-- first half of SHA256
0000a030: 1388 79a4 c9b3 b693 d0f3 6815 f2ac f6be  ..y.......h..... # <-- second half of SHA256
0000a040: ffff ffff ffff ffff ffff ffff ffff ffff  ................ # <-- start of padding
0000a050: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000a060: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000a070: ffff ffff ffff ffff ffff ffff ffff ffff  ................
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Todo
Development

Successfully merging a pull request may close this issue.

2 participants