Boot dm-verity Root and /usr with systemd

Getting dm-verity wrong at boot does not give you a warning, it gives you a machine that will not start. The systemd-veritysetup-generator deserves careful reading first. This guide walks through configuring, inspecting and troubleshooting it. The examples target the installed systemd 255 package, where the generator reads kernel command-line parameters and creates [email protected] units for protected root and /usr devices.

Allow 20 to 30 minutes for inspection and planning. You need console access, a bootloader configuration you can recover, and a trusted root hash. This guide does not format a device or enable verity on an existing installation; those are deployment operations with data-integrity and boot-failure consequences.

1. Confirm the installed behaviour

Check the package and the generator path before using documentation written for another release:

$ systemd --version
systemd 255 (255.4-1ubuntu8.17)
$ ls -l /usr/lib/systemd/system-generators/systemd-veritysetup-generator
-rwxr-xr-x 1 root root ... /usr/lib/systemd/system-generators/systemd-veritysetup-generator

The version string and file metadata vary by distribution. What matters is that systemd is version 255 here and the generator exists at the path named by the installed manpage. The generator is normally called by the system manager, not by passing it an interactive configuration file.

Checkpoint: record the version before comparing a parameter with another host. Root parameters were added in systemd 233, root options in 248, and the equivalent /usr parameters in 250. The installed release is new enough to have all three groups.

2. Inspect the current kernel command line

Start with a read-only check of what the running boot actually received:

$ tr ' ' '\n' < /proc/cmdline | grep -E '^(systemd\.verity|rd\.systemd\.verity|roothash|usrhash|systemd\.verity_root_|systemd\.verity_usr_)'
$ printf 'status: %s\n' "$?"
status: 1

Status 1 and no matching lines are expected on a machine not booted with these parameters. Do not read that as a failed verification; it means this boot did not ask the generator to configure a root or /usr mapping.

These are boot-time switches, not commands that change the current mapping.

3. Choose the root device description

For a protected root filesystem, provide roothash= with the trusted hexadecimal root hash. The documented automatic path works when the partition table follows systemd's convention: the data partition is located by a GPT partition UUID derived from the first 128 bits of the hash, and the hash partition by a UUID derived from the last 128 bits.

roothash=ROOT_HASH_HEX

ROOT_HASH_HEX is a placeholder, not a value to copy literally. The manpage expects a hexadecimal hash of the appropriate length, most commonly 64 characters for a 256-bit hash or longer. The hash must come from a trusted release process; a syntactically valid value from an untrusted source can make the system verify the wrong content.

If automatic GPT discovery does not match how the image is laid out, give both device paths explicitly:

systemd.verity_root_data=/dev/disk/by-partuuid/DATA_PARTUUID systemd.verity_root_hash=/dev/disk/by-partuuid/HASH_PARTUUID roothash=ROOT_HASH_HEX

The data and hash paths are separate settings. Keep the stable device names and exact hash in the image's release record. Do not guess a partition from its current /dev/sdX name, and do not omit the hash merely because the paths are explicit.

4. Add /usr only when the image requires it

The /usr group mirrors the root group: use usrhash=, systemd.verity_usr_data=, systemd.verity_usr_hash=, and systemd.verity_usr_options=. These settings configure a protected /usr filesystem, not an arbitrary extra data volume. The generator currently supports only root and /usr verity devices.

usrhash=USR_ROOT_HASH_HEX systemd.verity_usr_data=/dev/disk/by-partuuid/USR_DATA_PARTUUID systemd.verity_usr_hash=/dev/disk/by-partuuid/USR_HASH_PARTUUID

Use either automatic UUID derivation from the hash or explicit paths, matching the image layout. Keep root and /usr values together in the same boot-image configuration record so an update cannot accidentally pair a new hash with an old partition.

5. Match non-default dm-verity parameters

When the image was formatted with settings that differ from the defaults, pass the same comma-separated dm-verity options in systemd.verity_root_options= or systemd.verity_usr_options=:

systemd.verity_root_options=hash=sha256,data-block-size=4096,hash-block-size=4096

The generator's accepted option names include superblock, format, block sizes, data-blocks, hash-offset, salt, uuid, corruption handling flags, hash, FEC settings, and a root-hash signature. This option list was added in systemd 248 and is passed straight to the verity setup service. Use the exact values from the image-formatting record: a mismatch can prevent activation or make the image fail verification.

Warning: do not add ignore-corruption, restart-on-corruption or panic-on-corruption as a trial fix. Those options change the response to integrity failures and are security-sensitive policy decisions. Establish why the image was formatted with that behaviour before putting it in a boot parameter.

6. Reboot only after a recovery plan exists

Kernel parameters are normally supplied by the bootloader. Changing them is a privileged, security-sensitive operation that can make the host unbootable. Before editing the boot entry, save the previous entry or configuration, confirm out-of-band or physical console access, and keep a known-good root hash and device mapping available.

After the parameter is installed, reboot during a maintenance window. The generator runs early in boot and when the system manager reloads its configuration, then creates the required service instances. It does not itself format devices or permanently alter the partition table.

Recovery: to undo a test configuration, remove the added kernel parameters from the boot entry and reboot into the saved working configuration. If the system cannot boot, use the bootloader's one-time edit facility or recovery console to remove the parameters, then repair the persistent bootloader configuration once the host is running again.

7. Verify the generated service and failure reason

On the running host, look for instantiated verity setup services:

$ systemctl list-units --all 'systemd-veritysetup@*.service'
  UNIT                                      LOAD   ACTIVE   SUB     DESCRIPTION
  [email protected]         loaded active   exited  Disk verity protection logic

The exact instance name and state depend on the image and boot parameters. If the command prints no matching units, inspect /proc/cmdline again and check whether systemd.verity=no was supplied. An empty result is also correct when no verity parameters were present, as on an ordinary unprotected boot.

For a specific failure, inspect the service and this boot's journal:

$ systemctl status '[email protected]'
$ journalctl -b -u '[email protected]' --no-pager

These commands only read service state and logs. Use sudo only if the host's journal or service policy requires it. Look for a wrong hash, a missing data or hash device, an option mismatch, or an unavailable early-boot device. Do not respond to a verification error by enabling an ignore-corruption option; fix the image, partition mapping or trusted release metadata first.

Done means