@@ -785,6 +785,12 @@ struct UKIInfo {
785785 version : Option < String > ,
786786 os_id : Option < String > ,
787787 boot_digest : String ,
788+ /// The composefs image digest parsed from (and validated against) the UKI's
789+ /// own cmdline. This is the authoritative deployment key for UKI boots:
790+ /// setup-root opens `state/deploy/<this>` using the karg baked into the UKI,
791+ /// so the deploy directory must be named after exactly this value regardless
792+ /// of which EROFS format (V1 or V2) was sealed.
793+ composefs_cmdline : Sha512HashValue ,
788794}
789795
790796/// Writes a PortableExecutable to ESP along with any PE specific or Global addons
@@ -795,6 +801,7 @@ fn write_pe_to_esp(
795801 file_path : & Utf8Path ,
796802 pe_type : PEType ,
797803 uki_id : & Sha512HashValue ,
804+ boot_ids : & [ & Sha512HashValue ] ,
798805 missing_fsverity_allowed : bool ,
799806 mounted_efi : impl AsRef < Path > ,
800807) -> Result < Option < UKIInfo > > {
@@ -816,7 +823,7 @@ fn write_pe_to_esp(
816823 let composefs_info = BootComposefsCmdline :: < Sha512HashValue > :: from_cmdline ( & cmdline)
817824 . context ( "Parsing composefs=" ) ?
818825 . ok_or_else ( || anyhow:: anyhow!( "No composefs= or composefs.digest.v1= karg found in UKI cmdline" ) ) ?;
819- let composefs_cmdline = composefs_info. digest ( ) ;
826+ let composefs_cmdline = composefs_info. digest ( ) . clone ( ) ;
820827 let missing_verity_allowed_cmdline = composefs_info. is_insecure ( ) ;
821828
822829 // If the UKI cmdline does not match what the user has passed as cmdline option
@@ -835,11 +842,9 @@ fn write_pe_to_esp(
835842 _ => { /* no-op */ }
836843 }
837844
838- if * composefs_cmdline != * uki_id {
839- anyhow:: bail!(
840- "The UKI has the wrong composefs= parameter (is '{composefs_cmdline:?}', should be {uki_id:?})"
841- ) ;
842- }
845+ composefs_info
846+ . validate_digest ( boot_ids. iter ( ) . copied ( ) )
847+ . context ( "Validating UKI composefs digest" ) ?;
843848
844849 uki_reader. seek ( SeekFrom :: Start ( 0 ) ) ?;
845850 let osrel = uki:: get_text_section_buffered ( & mut uki_reader, ".osrel" ) ?;
@@ -856,6 +861,7 @@ fn write_pe_to_esp(
856861 version : parsed_osrel. get_version ( ) ,
857862 os_id : parsed_osrel. get_value ( & [ "ID" ] ) ,
858863 boot_digest,
864+ composefs_cmdline,
859865 } ) ;
860866 }
861867
@@ -889,8 +895,18 @@ fn write_pe_to_esp(
889895 let pe_dir = Dir :: open_ambient_dir ( & final_pe_path, ambient_authority ( ) )
890896 . with_context ( || format ! ( "Opening {final_pe_path:?}" ) ) ?;
891897
898+ // For UKIs, name the .efi file after the composefs cmdline digest (the
899+ // deploy key that setup-root uses), NOT the provisional uki_id. When the
900+ // UKI was sealed with --erofs-version=v1, uki_id (v2) and the cmdline
901+ // digest (v1) differ; using the cmdline digest keeps the filename, BLS
902+ // config, and state directory in agreement.
903+ let pe_name_owned;
892904 let pe_name = match pe_type {
893- PEType :: Uki => & get_uki_name ( & uki_id. to_hex ( ) ) ,
905+ PEType :: Uki => {
906+ let deploy_digest = & boot_label. as_ref ( ) . unwrap ( ) . composefs_cmdline ;
907+ pe_name_owned = get_uki_name ( & deploy_digest. to_hex ( ) ) ;
908+ & pe_name_owned
909+ }
894910 PEType :: UkiAddon => file_path
895911 . components ( )
896912 . last ( )
@@ -1071,8 +1087,9 @@ pub(crate) fn setup_composefs_uki_boot(
10711087 setup_type : BootSetupType ,
10721088 repo : crate :: store:: ComposefsRepository ,
10731089 id : & Sha512HashValue ,
1090+ boot_ids : & [ & Sha512HashValue ] ,
10741091 entries : Vec < ComposefsBootEntry < Sha512HashValue > > ,
1075- ) -> Result < String > {
1092+ ) -> Result < ( String , Sha512HashValue ) > {
10761093 let ( root_path, esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type
10771094 {
10781095 BootSetupType :: Setup ( ( root_setup, state, postfetch) ) => {
@@ -1152,7 +1169,8 @@ pub(crate) fn setup_composefs_uki_boot(
11521169 & entry. file ,
11531170 utf8_file_path,
11541171 entry. pe_type ,
1155- & id,
1172+ id,
1173+ boot_ids,
11561174 missing_fsverity_allowed,
11571175 esp_mount. dir . path ( ) ,
11581176 ) ?;
@@ -1169,17 +1187,30 @@ pub(crate) fn setup_composefs_uki_boot(
11691187
11701188 let boot_digest = uki_info. boot_digest . clone ( ) ;
11711189
1190+ // The deploy key for a UKI boot is the composefs digest baked into the UKI
1191+ // cmdline (already validated against `boot_ids` in `write_pe_to_esp`).
1192+ // setup-root opens `state/deploy/<this>` using that same karg, so we must
1193+ // key the deployment off exactly this value -- whether the UKI was sealed
1194+ // with the V2 (default) or V1 EROFS digest.
1195+ let deploy_id = uki_info. composefs_cmdline . clone ( ) ;
1196+
11721197 match bootloader {
1173- Bootloader :: Grub => {
1174- write_grub_uki_menuentry ( root_path, & setup_type, uki_info. boot_label , id, & esp_device) ?
1175- }
1198+ Bootloader :: Grub => write_grub_uki_menuentry (
1199+ root_path,
1200+ & setup_type,
1201+ uki_info. boot_label ,
1202+ & deploy_id,
1203+ & esp_device,
1204+ ) ?,
11761205
1177- Bootloader :: Systemd => write_systemd_uki_config ( & esp_mount. fd , & setup_type, uki_info, id) ?,
1206+ Bootloader :: Systemd => {
1207+ write_systemd_uki_config ( & esp_mount. fd , & setup_type, uki_info, & deploy_id) ?
1208+ }
11781209
11791210 Bootloader :: None => unreachable ! ( "Checked at install time" ) ,
11801211 } ;
11811212
1182- Ok ( boot_digest)
1213+ Ok ( ( boot_digest, deploy_id ) )
11831214}
11841215
11851216/// A composefs image attached to a temporary directory with the ESP and a
@@ -1350,6 +1381,15 @@ pub(crate) async fn setup_composefs_boot(
13501381 let id = composefs_oci:: generate_boot_image ( & repo, & pull_result. manifest_digest )
13511382 . context ( "Generating bootable EROFS image" ) ?;
13521383
1384+ // Open the OCI image to read both stored boot EROFS digests. The UKI may
1385+ // have been sealed with either the V1 or V2 boot image digest, so we need
1386+ // both for verification.
1387+ let oci_img =
1388+ composefs_oci:: oci_image:: OciImage :: open ( & * repo, & pull_result. manifest_digest , None )
1389+ . context ( "Opening OCI image to read boot image refs" ) ?;
1390+ let boot_id_v1 = oci_img. boot_image_ref_v1 ( ) . cloned ( ) ;
1391+ let boot_id_v2 = oci_img. boot_image_ref_v2 ( ) . cloned ( ) ;
1392+
13531393 // Reconstruct the OCI filesystem to discover boot entries (kernel, initramfs, etc.).
13541394 let fs = composefs_oci:: image:: create_filesystem ( & * repo, & pull_result. config_digest , None )
13551395 . context ( "Creating composefs filesystem for boot entry discovery" ) ?;
@@ -1402,25 +1442,49 @@ pub(crate) async fn setup_composefs_boot(
14021442 )
14031443 } ) ?;
14041444
1405- let boot_digest = match boot_type {
1406- BootType :: Bls => setup_composefs_bls_boot (
1407- BootSetupType :: Setup ( ( & root_setup, & state, & postfetch) ) ,
1408- repo,
1409- & id,
1410- entry,
1411- mounted_root. dir ( ) ,
1412- ) ?,
1445+ // The deployment key is the hash that setup-root looks for in
1446+ // state/deploy/<hash>, derived from the composefs karg. The two boot types
1447+ // establish that karg differently:
1448+ //
1449+ // * BLS: bootc writes the karg itself, so we are free to choose the key.
1450+ // Prefer the V2 digest (matching the default EROFS format), falling back
1451+ // to the primary id for legacy repos with no separate V2 boot ref.
1452+ //
1453+ // * UKI: the karg is baked into the UKI at seal time, so the key is
1454+ // whatever digest the UKI carries. `setup_composefs_uki_boot` parses and
1455+ // validates that against the boot refs and returns it, so we override the
1456+ // provisional value below. This keeps us correct whether the UKI was
1457+ // sealed with the V2 (default) or V1 EROFS digest.
1458+ let provisional_deploy_id = boot_id_v2. as_ref ( ) . cloned ( ) . unwrap_or_else ( || id. clone ( ) ) ;
1459+
1460+ // Collect whichever boot image refs exist; the UKI cmdline may carry either.
1461+ let boot_ids_owned: Vec < Sha512HashValue > =
1462+ [ boot_id_v1, boot_id_v2] . into_iter ( ) . flatten ( ) . collect ( ) ;
1463+ let boot_ids: Vec < & Sha512HashValue > = boot_ids_owned. iter ( ) . collect ( ) ;
1464+
1465+ let ( boot_digest, deploy_id) = match boot_type {
1466+ BootType :: Bls => (
1467+ setup_composefs_bls_boot (
1468+ BootSetupType :: Setup ( ( & root_setup, & state, & postfetch) ) ,
1469+ repo,
1470+ & provisional_deploy_id,
1471+ entry,
1472+ mounted_root. dir ( ) ,
1473+ ) ?,
1474+ provisional_deploy_id,
1475+ ) ,
14131476 BootType :: Uki => setup_composefs_uki_boot (
14141477 BootSetupType :: Setup ( ( & root_setup, & state, & postfetch) ) ,
14151478 repo,
1416- & id,
1479+ & provisional_deploy_id,
1480+ & boot_ids,
14171481 entries,
14181482 ) ?,
14191483 } ;
14201484
14211485 write_composefs_state (
14221486 & root_setup. physical_root_path ,
1423- & id ,
1487+ & deploy_id ,
14241488 & crate :: spec:: ImageReference :: from ( state. target_imgref . clone ( ) ) ,
14251489 None ,
14261490 boot_type,
0 commit comments