From 0ef8b7b453f767c0625bf84601c450e0d761353e Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Thu, 1 Oct 2026 21:28:11 +0000 Subject: [PATCH 1/4] disk layout: split USR-HASH into hash tree + reserved signature partition Replace the single 10 MiB HASH-A/HASH-B partitions with two partitions each: HASH-A/HASH-B (exact 9 MiB, verity hash tree, dps-usr-verity) and HASH-SIG-A/HASH-SIG-B (exact 1 MiB, reserved for a future verity root-hash signature, dps-usr-verity-sig). Add DPS_USR_VERITY_SIG_GUIDS to disk_util alongside the existing DPS_USR_VERITY_GUIDS constants, using the official UAPI Discoverable Partitions Specification GUIDs for the usr-verity-sig partition type (x86_64 and aarch64), so the new partitions are resolved the same way as the existing verity/root types instead of using the generic data GUID. Total space per slot is unchanged; subsequent partitions (OEM, ROOT) and all verity_hash/layout references are renumbered accordingly. --- acl/docs/architecture.md | 2 +- build_library/disk_layout_uki.json | 40 +++++++++++++++++++++++------- build_library/disk_util | 11 ++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/acl/docs/architecture.md b/acl/docs/architecture.md index 6339e9e7f70..5a119907dbb 100644 --- a/acl/docs/architecture.md +++ b/acl/docs/architecture.md @@ -53,7 +53,7 @@ ACL's primary boot path uses **systemd-boot** with **Unified Kernel Images (UKI) The `/usr` partition (USR-A) is a read-only btrfs filesystem with zstd compression. **dm-verity** provides block-level integrity verification: -- The verity hash tree is stored in a dedicated hash partition (HASH-A, immediately following USR-A on disk). +- The verity hash tree is stored in a dedicated hash partition (HASH-A, immediately following USR-A on disk). A separate, currently-unused 1 MiB partition (HASH-SIG-A) immediately follows HASH-A, reserved for a future verity root-hash signature. - At boot, `systemd-veritysetup` activates the verity device using slot-specific parameters delivered via a per-slot systemd-stub addon: `systemd.verity_usr_data=PARTUUID=`, `systemd.verity_usr_hash=PARTUUID=`, and `systemd.verity_usr_options=panic-on-corruption`. - The main UKI cmdline stays slot-independent (`mount.usr=/dev/mapper/usr`); Trident switches slots by swapping which addon is active in `.efi.extra.d/`, so the same signed UKI boots either A or B. (The secondary GRUB boot path is out of scope for A/B update and is unchanged: it still uses the inline PARTUUID + hash-offset verity cmdline on the existing non-UKI partition layout.) - Any corruption of `/usr` causes an immediate kernel panic, preventing the system from running a tampered image. diff --git a/build_library/disk_layout_uki.json b/build_library/disk_layout_uki.json index b4fb24edb37..e7c3d0e6d89 100644 --- a/build_library/disk_layout_uki.json +++ b/build_library/disk_layout_uki.json @@ -35,26 +35,42 @@ ] }, "3": { + "_comment": "9 MiB verity hash tree for USR-A.", "label": "HASH-A", "uuid": "b736baf1-cdb4-4535-beba-ddaaa30ad7b7", "type": "dps-usr-verity", - "blocks": "20480" + "blocks": "18432" }, "4": { + "_comment": "1 MiB reserved for a future USR-A verity root-hash signature.", + "label": "HASH-SIG-A", + "uuid": "3514648f-e3da-44ae-89ba-8d0552418f88", + "type": "dps-usr-verity-sig", + "blocks": "2048" + }, + "5": { "label": "USR-B", "uuid": "e03dd35c-7c2d-4a47-b3fe-27f15780a57c", "type": "flatcar-rootfs", "blocks": "2097152", "fs_blocks": "262144", - "verity_hash": "5" + "verity_hash": "6" }, - "5": { + "6": { + "_comment": "9 MiB verity hash tree for USR-B.", "label": "HASH-B", "uuid": "35bdf78b-c453-4661-98e6-f834f534ef5b", "type": "dps-usr-verity", - "blocks": "20480" + "blocks": "18432" }, - "6": { + "7": { + "_comment": "1 MiB reserved for a future USR-B verity root-hash signature.", + "label": "HASH-SIG-B", + "uuid": "d8941eb2-f713-4bb6-b4ae-bd8350ca27d4", + "type": "dps-usr-verity-sig", + "blocks": "2048" + }, + "8": { "label": "OEM", "fs_label": "OEM", "type": "data", @@ -63,7 +79,7 @@ "fs_compression": "zlib", "mount": "/oem" }, - "7": { + "9": { "label": "ROOT", "fs_label": "ROOT", "type": "dps-root", @@ -73,21 +89,21 @@ } }, "vm": { - "7": { + "9": { "label": "ROOT", "fs_label": "ROOT", "blocks": "12943360" } }, "azure": { - "7": { + "9": { "label": "ROOT", "fs_label": "ROOT", "blocks": "58875904" } }, "vagrant": { - "7": { + "9": { "label": "ROOT", "fs_label": "ROOT", "blocks": "33845248" @@ -113,6 +129,12 @@ "type": "blank" }, "7": { + "type": "blank" + }, + "8": { + "type": "blank" + }, + "9": { "label": "ROOT", "fs_label": "ROOT", "type": "0fc63daf-8483-4772-8e79-3d69d8477de4", diff --git a/build_library/disk_util b/build_library/disk_util index c8f64ef4ab6..772854ea5e0 100755 --- a/build_library/disk_util +++ b/build_library/disk_util @@ -39,6 +39,11 @@ DPS_USR_VERITY_GUIDS = { 'aarch64': '6E11A4E7-FBCA-4DED-B9E9-E1A512BB664E', } +DPS_USR_VERITY_SIG_GUIDS = { + 'x86_64': 'E7BB33FB-06CF-4E81-8273-E543B413E2E2', + 'aarch64': 'C23CE4FF-44BD-4B00-B2D4-B41B3419E02A', +} + # Map BOARD names used by the build system to machine architecture values. BOARD_TO_ARCH = { 'amd64-usr': 'x86_64', @@ -49,12 +54,14 @@ def _resolve_dps_types(config, arch): """Replace symbolic DPS partition type names with architecture-specific GUIDs. Currently supported placeholders: - dps-root → DPS root partition GUID for the target architecture - dps-usr-verity → DPS /usr verity hash partition GUID + dps-root → DPS root partition GUID for the target architecture + dps-usr-verity → DPS /usr verity hash partition GUID + dps-usr-verity-sig → DPS /usr verity signature partition GUID """ dps_map = { 'dps-root': DPS_ROOT_GUIDS, 'dps-usr-verity': DPS_USR_VERITY_GUIDS, + 'dps-usr-verity-sig': DPS_USR_VERITY_SIG_GUIDS, } for layout in config.get('layouts', {}).values(): for part in layout.values(): From 003880c4193e9dbe208c51138cb94c8195b8c95c Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Fri, 2 Oct 2026 03:25:23 +0000 Subject: [PATCH 2/4] disk layout: fix alignment padding gaps around HASH-SIG partitions disk_util aligns every partition start up to part_alignment (4096 blocks/2MiB) before placing it. Because HASH-A/HASH-B (18432 blocks = 9 MiB) and HASH-SIG-A/HASH-SIG-B (2048 blocks = 1 MiB) are not themselves multiples of 4096, the default alignment silently inserted four 1 MiB padding gaps (before each HASH-SIG and before USR-B/OEM) that belong to no partition and cannot be used - 4 MiB of dead space per disk image, contradicting the intent that splitting HASH into HASH+HASH-SIG not change total footprint. Override part_alignment to 2048 (1 MiB) on HASH-SIG-A/B specifically: they do not need 2 MiB alignment (small, non-performance-sensitive signature partitions), and since HASH-A + HASH-SIG-A (9 MiB + 1 MiB = 10 MiB) is itself a 2 MiB multiple, this removes the gap with no other side effects - every downstream partition offset (USR-B, HASH-B, HASH-SIG-B, OEM, ROOT) and the final disk image size now exactly match the original pre-split layout. Verified via disk_util.LoadPartitionConfig: zero gaps, and every partition offset after HASH-A matches the pre-split baseline exactly. --- build_library/disk_layout_uki.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build_library/disk_layout_uki.json b/build_library/disk_layout_uki.json index e7c3d0e6d89..6b55467d759 100644 --- a/build_library/disk_layout_uki.json +++ b/build_library/disk_layout_uki.json @@ -42,10 +42,11 @@ "blocks": "18432" }, "4": { - "_comment": "1 MiB reserved for a future USR-A verity root-hash signature.", + "_comment": "1 MiB reserved for a future USR-A verity root-hash signature. part_alignment is overridden to 1 MiB (vs. the 2 MiB default) so this partition packs directly after the 9 MiB HASH-A with no padding gap; the combined 10 MiB HASH-A+HASH-SIG-A still lands on a 2 MiB boundary for USR-B.", "label": "HASH-SIG-A", "uuid": "3514648f-e3da-44ae-89ba-8d0552418f88", "type": "dps-usr-verity-sig", + "part_alignment": "2048", "blocks": "2048" }, "5": { @@ -64,10 +65,11 @@ "blocks": "18432" }, "7": { - "_comment": "1 MiB reserved for a future USR-B verity root-hash signature.", + "_comment": "1 MiB reserved for a future USR-B verity root-hash signature. See HASH-SIG-A comment re: part_alignment override.", "label": "HASH-SIG-B", "uuid": "d8941eb2-f713-4bb6-b4ae-bd8350ca27d4", "type": "dps-usr-verity-sig", + "part_alignment": "2048", "blocks": "2048" }, "8": { From 3b5ef548e897229c57de327e20ddfc102abf0706 Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Fri, 2 Oct 2026 22:50:47 +0000 Subject: [PATCH 3/4] acl: address review feedback on HASH-SIG partition docs - disk_layout_uki.json: expand HASH-A/_B _comment with verity hash tree sizing rationale (sha256 + 4096-byte blocks assumption, ~8.08 MiB computed size vs 9 MiB allocated). Clarify blocks/part_alignment units are 512-byte sectors on the new HASH-SIG-A/B entries. - architecture.md: split the HASH-A/HASH-SIG-A bullet into two for readability. --- acl/docs/architecture.md | 3 ++- build_library/disk_layout_uki.json | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/acl/docs/architecture.md b/acl/docs/architecture.md index 5a119907dbb..84d6ada0b4a 100644 --- a/acl/docs/architecture.md +++ b/acl/docs/architecture.md @@ -53,7 +53,8 @@ ACL's primary boot path uses **systemd-boot** with **Unified Kernel Images (UKI) The `/usr` partition (USR-A) is a read-only btrfs filesystem with zstd compression. **dm-verity** provides block-level integrity verification: -- The verity hash tree is stored in a dedicated hash partition (HASH-A, immediately following USR-A on disk). A separate, currently-unused 1 MiB partition (HASH-SIG-A) immediately follows HASH-A, reserved for a future verity root-hash signature. +- The verity hash tree is stored in a dedicated hash partition (HASH-A, immediately following USR-A on disk). +- A separate, currently-unused 1 MiB partition (HASH-SIG-A) immediately follows HASH-A, reserved for a future verity root-hash signature. - At boot, `systemd-veritysetup` activates the verity device using slot-specific parameters delivered via a per-slot systemd-stub addon: `systemd.verity_usr_data=PARTUUID=`, `systemd.verity_usr_hash=PARTUUID=`, and `systemd.verity_usr_options=panic-on-corruption`. - The main UKI cmdline stays slot-independent (`mount.usr=/dev/mapper/usr`); Trident switches slots by swapping which addon is active in `.efi.extra.d/`, so the same signed UKI boots either A or B. (The secondary GRUB boot path is out of scope for A/B update and is unchanged: it still uses the inline PARTUUID + hash-offset verity cmdline on the existing non-UKI partition layout.) - Any corruption of `/usr` causes an immediate kernel panic, preventing the system from running a tampered image. diff --git a/build_library/disk_layout_uki.json b/build_library/disk_layout_uki.json index 6b55467d759..14b1753647b 100644 --- a/build_library/disk_layout_uki.json +++ b/build_library/disk_layout_uki.json @@ -35,14 +35,14 @@ ] }, "3": { - "_comment": "9 MiB verity hash tree for USR-A.", + "_comment": "9 MiB verity hash tree for USR-A (1 GiB). Assumes sha256 + 4096-byte data/hash blocks (dm-verity default): tree size is ~8.08 MiB (2048 level-0 hash blocks + 17 upper-level blocks + superblock), so 9 MiB leaves headroom for growth. Recompute if USR-A size or verity hash/block params change.", "label": "HASH-A", "uuid": "b736baf1-cdb4-4535-beba-ddaaa30ad7b7", "type": "dps-usr-verity", "blocks": "18432" }, "4": { - "_comment": "1 MiB reserved for a future USR-A verity root-hash signature. part_alignment is overridden to 1 MiB (vs. the 2 MiB default) so this partition packs directly after the 9 MiB HASH-A with no padding gap; the combined 10 MiB HASH-A+HASH-SIG-A still lands on a 2 MiB boundary for USR-B.", + "_comment": "1 MiB reserved for a future USR-A verity root-hash signature. blocks/part_alignment are 512-byte sectors (see metadata.block_size); part_alignment is overridden to 2048 sectors (1 MiB, vs. the file-wide 4096-sector/2 MiB default) so this partition packs directly after HASH-A with no gap, while the combined 10 MiB HASH-A+HASH-SIG-A still lands on the 2 MiB boundary required by USR-B.", "label": "HASH-SIG-A", "uuid": "3514648f-e3da-44ae-89ba-8d0552418f88", "type": "dps-usr-verity-sig", @@ -58,14 +58,14 @@ "verity_hash": "6" }, "6": { - "_comment": "9 MiB verity hash tree for USR-B.", + "_comment": "9 MiB verity hash tree for USR-B. See HASH-A comment for sizing rationale.", "label": "HASH-B", "uuid": "35bdf78b-c453-4661-98e6-f834f534ef5b", "type": "dps-usr-verity", "blocks": "18432" }, "7": { - "_comment": "1 MiB reserved for a future USR-B verity root-hash signature. See HASH-SIG-A comment re: part_alignment override.", + "_comment": "1 MiB reserved for a future USR-B verity root-hash signature. See HASH-SIG-A comment for units and part_alignment rationale.", "label": "HASH-SIG-B", "uuid": "d8941eb2-f713-4bb6-b4ae-bd8350ca27d4", "type": "dps-usr-verity-sig", From 4af23f0276b537b43758adee68173ba719391f05 Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Mon, 5 Oct 2026 18:55:21 +0000 Subject: [PATCH 4/4] docs: fix ROOT partition verification commands for new disk layout The usr-hash-sig-a/b partitions shift ROOT from the 7th to the 9th partition on the new layout. Hard-coding `nbd0p7` and `max_part=8` broke the documented verification steps. Select ROOT by GPT partition label instead of a hard-coded index, so the commands work for both the old and new layouts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- acl/docs/containerd-image-preload.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/acl/docs/containerd-image-preload.md b/acl/docs/containerd-image-preload.md index aac5d3650ba..60f1aceb082 100644 --- a/acl/docs/containerd-image-preload.md +++ b/acl/docs/containerd-image-preload.md @@ -323,10 +323,15 @@ actually recovers the store. ### Inspect the output image without booting ```sh -sudo modprobe nbd max_part=8 +sudo modprobe nbd max_part=9 sudo qemu-nbd --connect=/dev/nbd0 --read-only -f vpc staging/out/acl-preloaded.vhd sudo mkdir -p /mnt/verify -sudo mount -o ro /dev/nbd0p7 /mnt/verify # ROOT is the seventh partition + +# Select ROOT by GPT partition label rather than a hard-coded index -- the +# partition number varies by layout (e.g. ROOT is p7 on the pre-usr-verity-sig +# layout, p9 once hash-sig-a/hash-sig-b are present). +ROOT_PART=$(sudo blkid -t PARTLABEL="ROOT" -o device /dev/nbd0p* | head -n1) +sudo mount -o ro "${ROOT_PART}" /mnt/verify ls -la /mnt/verify/var/lib/containerd strings /mnt/verify/var/lib/containerd/io.containerd.metadata.v1.bolt/meta.db \