feat(ext): image.verity - dm-verity hash tree and root hash for extension images - #220
Open
mobileoverlord wants to merge 4 commits into
Open
feat(ext): image.verity - dm-verity hash tree and root hash for extension images#220mobileoverlord wants to merge 4 commits into
mobileoverlord wants to merge 4 commits into
Conversation
…sion images
`image: { verity: true }` on an extension makes `avocado ext image` run
`veritysetup format` over the finished filesystem image, producing a hash-tree
sidecar (<stem>.verity) and the hex root hash (<stem>.roothash) next to it.
The runtime build carries both alongside the image and writes the root hash
into the manifest entry as `root_hash`, with the sidecar landing as
<image_id>.verity - exactly what avocadoctl's ManifestExtension::
resolve_verity_path and VeritySpec (avocado-control #22) consume through the
single systemd-dissect call site, for raw and KAB alike.
Shape: a sibling of image.type, not a type value - verity is orthogonal to the
container format (the KAB layer is the same bytes, so one tree serves both).
A bool for now because avocadoctl's contract fixes every parameter (sha256,
manifest-carried hash); it can grow to `bool | { sign: .., hash: .. }` without
breaking `verity: true`. Lives under image: so rootfs/initramfs later use the
same key, and stamps.rs already folds image: into the build hash.
Salt is a fixed zero: the manifest, not the sidecar, is the trust anchor, so a
public salt costs nothing and buys reproducible root hashes for identical
image bytes. Inert for every existing config (field absent -> no step, no
sidecars, manifest unchanged).
There was a problem hiding this comment.
Pull request overview
Adds producer-side support in avocado ext image and runtime manifest/build tooling to generate and propagate dm-verity artifacts (hash tree + manifest-carried root hash) for extension images, controlled via a new extensions.<name>.image.verity: true config flag.
Changes:
- Introduces
image.verityconfig parsing for extensions. - Ext image build script can optionally run
veritysetup formatto emit*.verityand*.roothashsidecars. - Runtime build copies verity sidecars and injects
root_hashinto the generated extension manifest entries (when sidecars are present), with a unit test covering script emission.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/utils/config.rs |
Adds get_ext_image_verity() to read extensions.*.image.verity (default false). |
src/commands/ext/image.rs |
Threads verity into the image build script and conditionally emits a veritysetup format step; updates tests for the new argument. |
src/commands/runtime/build.rs |
Copies *.verity/*.roothash sidecars into runtime staging and records root_hash + sidecar path in the manifest generation logic. |
Suppressed comments (2)
src/commands/runtime/build.rs:1459
- Same stale-sidecar risk as above for versioned external extensions: the destination
.verity/.roothashfiles can linger across rebuilds and then be incorrectly included in the manifest. Remove the destination sidecars before the conditional copy loop.
for sc in verity roothash; do
[ -f "$AVOCADO_PREFIX/output/extensions/{versioned_name}.$sc" ] && cp -f "$AVOCADO_PREFIX/output/extensions/{versioned_name}.$sc" "$RUNTIME_EXT_DIR/{versioned_name}.$sc"
done
src/commands/runtime/build.rs:1475
- Same stale-sidecar risk as above for the wildcard
.rawextension copy: if$RUNTIME_EXT_DIR/${EXT_BASENAME%.raw}.verity/.roothashalready exist, they’ll remain when the source sidecars are absent and can be incorrectly consumed later. Remove destination sidecars before conditionally copying.
for sc in verity roothash; do
[ -f "${{EXT_FILE%.raw}}.$sc" ] && cp -f "${{EXT_FILE%.raw}}.$sc" "$RUNTIME_EXT_DIR/${{EXT_BASENAME%.raw}}.$sc"
done
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
lee-reinhardt
requested changes
Aug 26, 2026
…in the runtime build
`rootfs: { image: { verity: true } }` makes the rootfs build run `veritysetup
format` over the finished image: `<image>.verity` (hash tree, consumed by the
stone manifest as a per-slot hash partition image) and `<image>.roothash`,
exported as AVOCADO_ROOTFS_ROOTHASH and recorded as `root_hash` on the
manifest's rootfs entry.
The runtime build now assembles `fitImage` itself on machines whose feed ships
a `fit-image.its` template: the template's ramdisk node is pointed at this
runtime's initramfs (until now a FIT machine booted the feed's initramfs, so
`initramfs:` config never reached the device), the rootfs root hash is added to
every configuration node as `avocado,roothash` so the U-Boot env can read it
back after verification and put it on the kernel cmdline, and the result is
signed with the project's key when AVOCADO_FIT_KEY_DIR (host dir holding
FIT.key/FIT.crt, bind-mounted read-only) is set - otherwise the signature
nodes are stripped and the FIT is unsigned. No template: no-op.
…ble tree, no stale sidecars, guarded publish paths - image.verity must be a boolean; a present non-bool value is an error rather than silently reading as off (config test added). - veritysetup format gets a fixed --uuid alongside the fixed salt, so the tree itself is reproducible, not only the root hash. - The runtime manifest step takes the verity flag from config through AVOCADO_EXT_PAIRS (name:version:type:verity|plain) instead of inferring it from sidecar presence: with the flag set both sidecars are required, without it they are ignored. - Stale sidecars cannot survive: the image build removes them when verity is off, the staging copies remove the destination before the conditional copy, ext clean removes them, and the stale-image sweep covers <image_id>.verity. - avocado deploy and avocado connect upload refuse a runtime whose manifest carries root_hash: neither publishes the hash trees yet, and a device given a root hash without its tree refuses the extension. Provisioning carries everything. - KAB ordering test: the verity step precedes the KAB wrap. - CHANGELOG entry under Unreleased / Added.
…build an unsigned FIT by default The rootfs verity block and the FIT assembly run without set -e in the runtime build, so: veritysetup failure and an empty root hash are fatal; mkimage failure and a missing fitImage are fatal; both sed rewrites of the template (initramfs path, avocado,roothash) are verified to have matched, and the no-op ramdisk substitution is gone. The FIT is rebuilt only when it can be signed (AVOCADO_FIT_KEY_DIR) or an unsigned FIT is explicitly requested (AVOCADO_FIT_UNSIGNED=1): a distro built with verified-boot embeds its key in U-Boot, where an unsigned FIT would not boot. With neither, the feed's FIT is left in place (with a warning that this runtime's initramfs is not in it), and rootfs verity - which needs the rebuild - is an error rather than a silently unverified boot. Tests sh -n the generated scripts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
image.verity: truefor extensions and for the rootfs:avocado ext image/ the rootfs build produce a dm-verity hash tree; the runtime manifest carries the extension root hashes; the boot FIT carries the rootfs root hash. Consumer sides: avocado-control #22 (extensions), meta-avocado #316 (kernel/U-Boot capability, hash partitions, env).Scope: provisioning.
avocado provisioncarries the hash trees (whole var-staging tree + hash partition).avocado deployandavocado connect uploaddo not publish<image_id>.verityyet and therefore refuse a runtime whose manifest carriesroot_hash; publishing the trees as targets is a follow-up.Config
Sibling of
typebecause verity is orthogonal to the container format (avocadoctl applies--root-hash/--verity-dataat its single dissect call for raw and KAB alike). A bool because the consumers fix every parameter; it can grow tobool | { … }later. Underimage:so every image kind spells it the same way andstamps.rsalready hashes it.How
Extensions — after
mkfs(before any KAB wrap):veritysetup format --salt=0…0→<stem>.verity,<stem>.roothash. The runtime build copies both next to the image, writesroot_hashinto the manifest entry, stores the tree as<image_id>.verity(the pathManifestExtension::resolve_verity_pathderives). Fixed zero salt: the manifest is the trust anchor, so a public salt buys reproducible root hashes.Rootfs — same producer; the tree is written as
<rootfs>.verityfor the stone manifest's per-slot hash partition (systemd's veritysetup generator wants a block device, no hash-offset option), andAVOCADO_ROOTFS_ROOTHASHis recorded on the manifest's rootfs entry.Boot FIT — the runtime build now assembles
fitImagefrom the feed'sfit-image.itstemplate on machines that ship one: the template's ramdisk node is pointed at this runtime's initramfs (until now a FIT machine booted the feed's initramfs, soinitramfs:config never reached the device), the rootfs root hash is added to every configuration node asavocado,roothash(read back by the U-Boot env after verification intoroothash=on the cmdline), and the FIT is signed with the project key fromAVOCADO_FIT_KEY_DIR(host dir withFIT.key/FIT.crt, bind-mounted read-only). The rebuild happens only when that key is set orAVOCADO_FIT_UNSIGNED=1explicitly asks for an unsigned FIT (averified-bootdistro embeds its key in U-Boot, where an unsigned FIT would not boot); with neither, the feed's FIT is left untouched and a warning notes the project initramfs is not in it. Rootfs verity needs the rebuild, so it errors out in that case. No template → no-op. Noverityfield and no FIT env → nothing changes.Verified on imx8mp-evk
verity: trueloop*-verity, root hash = manifest,status: verifieddm-verity: data block 512 is corrupted, extension refused, system boots.veritysidecar removedverity: true, project-signed FITroot=/dev/mapper/root roothash=…,veritysetup status root → verifiedoverrootfs-a+rootfs-a-hash,/erofsdm-verity: 179:5: data block 8192 is corrupted, reads of that block return EIORegression tests:
test_verity_step_emitted_only_when_requested(incl. KAB ordering),test_rootfs_verity_step_is_opt_in_and_exports_root_hash,fit_assembly_script_is_gated_and_handles_signing_both_ways,image_verity_is_strict_bool; generated scripts aresh -nchecked.Review follow-ups landed in 6488ee4 and ccf2bb3: strict bool config, fixed
--uuid, verity flag carried explicitly throughAVOCADO_EXT_PAIRS, no stale sidecars (image build, staging,ext clean, sweep), fail-closedveritysetup/mkimage, checked template rewrites, deploy/upload refusal, CHANGELOG.