Mender, CM4, `dm-verity`

Hello there!

I am, with a lot of help from Claude, working on a secure boot chain for the CM4 with dm-verity. Once I get it working my plan is to untangle it from our proprietary layer and make it into its own, open source layer.

As has been discussed on this forum previously, we need a custom update module that will find the underlying block device, and the dm-verity root hash cannot go on in rootfs (because chicken/egg) nor in a single location (because A/B updates).

Claude proposed a per-slot FIT in boot.img containing the root hash. Which made me think perhaps we should move the kernel into that same file (rather than /boot/fitImage). Thoughts? One disadvantage is that the kernel is not protected by dm-verity but I am not sure this is an issue in practice (I don’t think there’s a splice attack).

In this case the layout would be as follows:

  FAT (kernel lives here, per slot)
                                                                                                                                                                                                                                                                                                                            
  config.txt        15
  boot.img    3,213,312                                                                                                                                                                                                                                                                                                     
  boot.sig          602                                                                                                                                                                                                                                                                                                     
  boot.scr        4,756                                                                                                                                                                                                                                                                                                     
  boot-2.fit 10,848,705                                                                                                                                                                                                                                                                                                     
  boot-3.fit 10,848,705                                                                                                                                                                                                                                                                                                     
                         109 MB free of 128 MB
                                                                                                                                                                                                                                                                                                                            
  boot.fit content summary                                                                                                                                                                                                                                                                                                  
  
  FIT description: xebra slot boot (kernel + dtb stub + bootargs script)                                                                                                                                                                                                                                                    
   Image 0 (kernel)   gzip,  10,788,495 B (10.29 MiB)  AArch64/Linux  load=0x20008000  sha256                                                                                                                                                                                                                               
   Image 1 (fdt)      none,      56,928 B (55.59 KiB)  AArch64        sha256                                                                                                                                                                                                                                                
   Image 2 (script)   none,         552 B              Script         sha256                                                                                                                                                                                                                                                
   Default Configuration: 'conf-1'                                                                                                                                                                                                                                                                                          
   Configuration 0 (conf-1)                                                                                                                                                                                                                                                                                                 
    Kernel:       kernel                                                                                                                                                                                                                                                                                                    
    FDT:          fdt
    (script:      script)              ← present per fdtget (dumpimage doesn't print it)                                                                                                                                                                                                                                    
    Sign algo:    sha256,rsa2048:boot_img                                                                                                                                                                                                                                                                                   
    Sign padding: pkcs-1.5                                                                                                                                                                                                                                                                                                  
    sign-images:  kernel fdt script    ← all three covered by one signature                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                            
  .mender artifact payload                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                            
  Type: rootfs-verity
  Files:                                                                                                                                                                                                                                                                                                                    
    rootfs.ext4.verity   2,654,769,152 B   sha256 e06927fc...
    boot.fit                10,848,705 B   sha256 81c2d91c...                                                                                                                                                                                                                                                               
  2 payload files, no separate fitImage. Signature on artifact present.
                                                                                                                                                                                                                                                                                                                            

Cheers,
Luke

Putting this in a separate post because it is Claude-generated (with some cleanups by me):

Secure-boot chain on RPi CM4 (with dm-verity, no initramfs)

End-to-end, every link verified by the previous one:

  1. VideoCore ROM verifies boot.img (the inner FAT image holding GPU firmware, U-Boot, base DTB, overlays, cmdline.txt, config.txt) against an RSA pubkey hash fused into OTP. That OTP fuse + SIGNED_BOOT=1 in EEPROM is what makes the CM4 a hardware root of trust.

  2. U-Boot runs from inside boot.img and sources a signed boot.scr from the outer FAT. boot.scr is a FIT-wrapped Hush script with a signature-1 node; U-Boot’s source command verifies it against the RSA pubkey embedded in U-Boot’s control DTB (u-boot.dtb) and refuses tampered or unsigned scripts.

  3. boot.scr loads a per-slot signed FIT from the FAT named boot ${mender_boot_part}.fit (one per Mender A/B slot). This is the ‘interesting’ (Claude’s words) bit: it’s a single signed FIT containing three sub-images — gzipped Linux kernel, a stub DTB (covered by the signature; runtime DTB comes from VC firmware via bootm’s external-FDT arg), and a Hush bootargs script with the per-build dm-verity roothash baked into its signed bytes. One configurations.conf-1carries kernel/fdt/script properties; one signature-1 covers all three. boot.scr does:

    load mmc 0:1 ${kernel_addr_r} boot-${mender_boot_part}.fit
    source ${kernel_addr_r}                  # verifies sig, runs script
    fdt move ${fdt_addr} ${fdt_addr_r}
    bootm ${kernel_addr_r} - ${fdt_addr_r}   # verifies same sig, boots
    

    source and bootm both resolve the default config and verify the same signature node — two consumers, one signature, one atomic per-slot file.

  4. The signed script sets the kernel cmdline (setenv bootargs '... dm-mod.create="..." ${mender_kernel_root} ... root=/dev/dm-0 ...'). The script is wrapped in single quotes so Hush doesn’t eat ${mender_kernel_root} at source time; CONFIG_BOOTARGS_SUBST=y makes bootm substitute it in place without re-tokenising, so the embedded dm-mod.create="..." double quotes survive into /proc/cmdline.

  5. The kernel boots with CONFIG_DM_INIT, which parses dm-mod.create= at late_initcall and brings up /dev/dm-0 before rootfs mount. Every block read from the rootfs is then verified against the signed roothash. No initramfs.

  6. Mender OTA ships a single .mender artifact carrying rootfs.ext4.verity + boot.fit. A custom Update Module streams the rootfs to the inactive partition and boot.fit to /uboot/boot-${passive}.fit on the FAT, then flips mender_boot_part. New slot has a matching {rootfs, kernel, roothash} triple, signed by the same RSA key.

Same RSA key (UBOOT_SIGN_KEYNAME) signs boot.scr, boot-${slot}.fit, and the kernel image inside it. (Luke notes: re-using the key may not be ideal?)

The public key lives in U-Boot’s control DTB, which is itself baked into the U-Boot binary inside VC-signed boot.img. Trust roots in OTP and propagates forward without ever relying on writable storage state (env vars, unsigned scripts, mutable boot partitions).

Hi @lukehatpadl,

Thanks for getting this started! I’m also looking into the topic a bit, and I actually think that the approach by Claude is not the right way to go here. Especially the part which repeatedly modifies the boot partition is not a good idea, I would say.

What it did get right:

  • the kernel+initrd should be bundled in a signed fitImage, which gets verified by u-boot, and only loaded+executed if the check was successful.
  • from there, the kernel then uses a dm-verity signed root filesystem, a read only one strongly preferred.
  • Mender ships a root filesystem+fitImage artifact, which mandates a new Update Module to be created

Where it did go wrong:

Technically a more RPi-specific structure could even be used, like the tryboot flow (pre-Alpha quality PoC at feat: Raspberry Pi tryboot A/B support by TheYoctoJester · Pull Request #501 · mendersoftware/meta-mender-community · GitHub), but such a fitImage+dm-verity flow should be a pretty generic concept in my opinion, and I’m looking into creating such an approach. Hopefully I can show something soon.

Greetz,
Josef

Some brief thoughts (away from desk this week so, brief).

  • dm-verity without initramfs seems to work fine here (see here)
  • having a FIT image on the boot partition with the root hash and kernel, vs A/B kernel partitions seems a matter of taste? at the end of the day, a supported third-party layer trumps taste for me, though
  • using the OTP ECDSA key seems very neat, so yes, but also not necessary cryptographically if we have a secure boot chain with a signed boot loader
  • tryboot I suspect would boot faster but, less portable. See also this upcoming talk (although I know they don’t want to support Mender, and FDE is not something I want or need)

If you want to reach out of email (lukeh at lukktone dot com) I can share my changes.

Hi @lukehatpadl,

Matching your brief thoughts:

  • yes, dm-verity in a fitImage without initramfs might work in this specific (RPi) case, but my gut feeling is that for a generic solution it (or a similar approach) is required
  • the question always is, what is the trust anchor for the signed boot loader
  • yes, I’m aware of Ayoub’s work (in fact, I reviewed and was involved with accepting that presentation :slight_smile: ). But again as you rightfully say, not generic/portable.

I’ll get in touch with you, definitely interested in how and what you put together.

Greetz,
Josef