diff --git a/src/commands/runtime/deploy.rs b/src/commands/runtime/deploy.rs index 6113fd45..3bb0a5a1 100644 --- a/src/commands/runtime/deploy.rs +++ b/src/commands/runtime/deploy.rs @@ -701,10 +701,33 @@ fi # Extension dm-verity trees (.verity) are not published by this path # yet - only the images are - and a device given a root_hash without its tree # refuses the extension. Refuse here instead, where the author can act on it. -if grep -q '"root_hash"' "$MANIFEST_FILE"; then - echo "ERROR: this runtime has extensions with image.verity: true; deploy does not publish their dm-verity hash trees yet, so the device would refuse them. Provision instead, or build without verity." >&2 - exit 1 -fi +# Only EXTENSION root hashes matter: the manifest's top-level root_hash is the +# rootfs verity hash, which travels in the boot FIT and needs no sidecar here. +# Exit 0: an extension carries root_hash (key present, as `connect upload` +# tests it). Exit 1: none does. Exit 2: the manifest could not be read or is +# not the expected shape - that must stop the deploy too, never pass as "no +# verity". +python3 -c ' +import json, sys +try: + m = json.load(open(sys.argv[1])) + exts = m["extensions"] + if not isinstance(exts, list): + raise TypeError("extensions is not a list") +except Exception as e: + print("manifest %s: %s" % (sys.argv[1], e), file=sys.stderr) + sys.exit(2) +sys.exit(0 if any(isinstance(e, dict) and "root_hash" in e for e in exts) else 1) +' "$MANIFEST_FILE" +case $? in + 0) + echo "ERROR: this runtime has extensions with image.verity: true; deploy does not publish their dm-verity hash trees yet, so the device would refuse them. Provision instead, or build without verity." >&2 + exit 1 ;; + 1) ;; + *) + echo "ERROR: could not read the runtime manifest to check for extension verity" >&2 + exit 1 ;; +esac # Read root.json ROOT_JSON_FILE="$VAR_STAGING/lib/avocado/metadata/root.json" @@ -1139,6 +1162,31 @@ async fn prepare_mac_deploy_net( #[cfg(test)] mod tests { + #[test] + fn hash_collection_refuses_only_extension_root_hashes() { + // A rootfs verity hash lives at the manifest's top level and needs no + // sidecar; only extensions[].root_hash must trip the refusal. + let cmd = RuntimeDeployCommand::new( + "dev".to_string(), + "avocado.yaml".to_string(), + false, + None, + "root@host".to_string(), + None, + None, + ); + let s = cmd.create_hash_collection_script("x86_64"); + assert!( + s.contains("\"root_hash\" in e for e in exts"), + "checks key presence on extensions, not the whole file" + ); + assert!( + s.contains("sys.exit(2)"), + "an unreadable manifest is its own failure" + ); + assert!(!s.contains("grep -q '\"root_hash\"'"), "no whole-file grep"); + } + use super::*; // --- DeviceSpec parsing tests ---