FIT dm-verity Boot Parameters

Introduction

Linux’s dm-verity device-mapper target provides transparent integrity checking of block devices using a Merkle tree. It is commonly used to protect read-only root filesystems such as SquashFS images.

When a FIT image packages the root filesystem as a loadable sub-image of type filesystem (IH_TYPE_FILESYSTEM), the verity metadata can be stored alongside the image data in a dm-verity subnode. U-Boot reads this metadata at boot time and generates the kernel command-line parameters that Linux needs to activate the verity target, eliminating the need for an initramfs or userspace helper to set up dm-verity.

This feature is enabled by CONFIG_FIT_VERITY (see boot/Kconfig).

Prerequisites

  • Linux uImage.FIT block driver – the kernel must include the FIT block driver that exposes loadable sub-images as /dev/fit0, /dev/fit1, etc. The driver assigns device numbers in the order loadables appear in the FIT configuration.

  • dm-verity support in the kernelCONFIG_DM_VERITY must be enabled so the kernel can process the dm-mod.create= parameter.

  • CONFIG_FIT_VERITY enabled in U-Boot.

How it works

The implementation is split into a build phase and an apply phase, both of which run automatically within the bootm state machine. No boot method needs to call verity functions explicitly.

Build phase (BOOTM_STATE_FINDOTHERboot_get_loadable())

  1. After all loadable sub-images have been loaded, fit_verity_build_cmdline() iterates the configuration’s loadables list.

  2. For each loadable that is an IH_TYPE_FILESYSTEM image and contains a dm-verity child node, a dm-verity target specification is built by the helper fit_verity_build_target().

  3. The dm-verity target references /dev/fitN, where N is the zero-based index of the loadable in the configuration. This matches the numbering used by the Linux FIT block driver.

  4. The resulting fragments are stored in struct bootm_headers:

    images->dm_mod_create

    The full dm-verity target table. Multiple targets are separated by ;.

    images->dm_mod_waitfor

    Comma-separated list of /dev/fitN devices so the kernel waits for the underlying FIT block devices to appear before activating device-mapper.

Apply phase (BOOTM_STATE_OS_PREP)

  1. Just before bootm_process_cmdline_env() processes the bootargs environment variable, fit_verity_apply_bootargs() appends the dm-mod.create= and dm-mod.waitfor= parameters.

Bootmeth integration

Because the fragments are stored in struct bootm_headers, a boot method can check fit_verity_active(images) between bootm state invocations. A typical pattern splits bootm_run_states() into two calls – one for START|FINDOS|FINDOTHER|LOADOS and one for OS_PREP|OS_GO – and inspects fit_verity_active() in between to decide whether to add a root= parameter pointing at the dm-verity device.

FIT image source (.its) example

Below is a minimal .its file showing a kernel and a dm-verity-protected root filesystem packaged as a FIT. Only the three user-provided properties (algo, data-block-size, hash-block-size) are included; mkimage computes and fills in digest, salt, num-data-blocks, and hash-start-block automatically (see Generating verity metadata below):

/dts-v1/;

/ {
    description = "Kernel + dm-verity rootfs";
    #address-cells = <1>;

    images {
        kernel {
            description = "Linux kernel";
            data = /incbin/("./Image.gz");
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "gzip";
            load = <0x44000000>;
            entry = <0x44000000>;
            hash-1 {
                algo = "sha256";
            };
        };

        fdt {
            description = "Device tree blob";
            data = /incbin/("./board.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash-1 {
                algo = "sha256";
            };
        };

        rootfs {
            description = "SquashFS root filesystem";
            data = /incbin/("./rootfs.squashfs");
            type = "filesystem";
            arch = "arm64";
            compression = "none";
            hash-1 {
                algo = "sha256";
            };

            dm-verity {
                algo = "sha256";
                data-block-size = <4096>;
                hash-block-size = <4096>;
            };
        };
    };

    configurations {
        default = "config-1";
        config-1 {
            description = "Boot with dm-verity rootfs";
            kernel = "kernel";
            fdt = "fdt";
            loadables = "rootfs";
        };
    };
};

With this configuration U-Boot produces a kernel command line similar to:

dm-mod.create="rootfs,,, ro,0 <data_sectors> verity 1 \
    /dev/fit0 /dev/fit0 4096 4096 3762 3762 sha256 \
    8e6791637f93cbb81fc45299e203cbe85ca2e47a38f5051bddeece92d7b1c9f9 \
    aa7b11f8db8fe2e5bfd4eca1d18a22b5de7ea39d2e1b93bb7272ce0c6ca3cc8e" \
dm-mod.waitfor=/dev/fit0

dm-verity subnode properties

User-provided properties (required in the .its):

Property

Type

Description

algo

string

Hash algorithm name, e.g. "sha256".

data-block-size

u32

Data block size in bytes (>= 512, typically 4096).

hash-block-size

u32

Hash block size in bytes (>= 512, typically 4096).

Computed properties (filled in by mkimage):

Property

Type

Description

num-data-blocks

u32

Number of data blocks in the filesystem image (computed from the image size and data-block-size).

hash-start-block

u32

Offset in hash-block-size-sized blocks from the start of the sub-image to the root block of the hash tree.

digest

byte array

Root hash of the Merkle tree, stored as raw bytes. Length must match the output size of algo.

salt

byte array

Salt used when computing the Merkle tree, stored as raw bytes.

These values are the same ones produced by veritysetup format and can typically be obtained from its output. The digest and salt byte arrays correspond to the hex-encoded Root hash and Salt printed by veritysetup format.

Optional boolean properties (when present, they are collected and appended as dm-verity optional parameters with hyphens converted to underscores):

Property

Description

restart-on-corruption

Restart the system on data corruption.

panic-on-corruption

Panic the system on data corruption.

restart-on-error

Restart the system on I/O error.

panic-on-error

Panic the system on I/O error.

check-at-most-once

Verify data blocks only on first read.

Generating verity metadata

mkimage automates the entire process. When it encounters a dm-verity subnode, it:

  1. Writes the embedded image data to a temporary file.

  2. Runs veritysetup format with the user-supplied algorithm and block sizes.

  3. Parses Root hash and Salt from veritysetup stdout.

  4. Reads the expanded content (original data + Merkle hash tree) back into an in-memory buffer and removes the temporary file. The external-data section written to the .itb file uses this buffer in place of the original data property.

  5. Writes the computed digest, salt, num-data-blocks, and hash-start-block properties into the dm-verity subnode.

Images with dm-verity subnodes must use external data layout (mkimage -E). mkimage will abort with an error if -E is not specified.

Usage:

# Create the filesystem image
mksquashfs rootfs/ rootfs.squashfs -comp xz

# Build the FIT (dm-verity is computed automatically); align each
# external-data section to the block size of the underlying storage
# (see the alignment note below).
mkimage -E -B 0x1000 -f image.its image.itb

veritysetup (from the cryptsetup package) must be installed on the build host.

Note

veritysetup format is invoked with --no-superblock, so no on-disk superblock is written between the data and hash regions. The Merkle hash tree is appended directly to the image data within the FIT external data section. hash-start-block is therefore computed as data_size / hash-block-size (the offset of the hash region in units of hash-block-size). When data-block-size equals hash-block-size this happens to equal num-data-blocks.

Note

The Linux fitblk driver currently requires each filesystem sub-image to start and end on block boundaries of the underlying block device (typically 512 bytes, sometimes 4 KiB for eMMC or NVMe with 4 KiB native sectors). Use mkimage -B <align> to pad external-data sections to that boundary; -B 0x1000 is a safe default for the storage in common use.

This alignment requirement comes from the kernel-side fitblk driver to avoid unaligned-access fix-up overhead in block I/O, and is independent of the dm-verity data-block-size and hash-block-size properties – those describe the block sizes used by the device-mapper verity target itself, not storage alignment.

Kconfig

CONFIG_FIT_VERITY

Depends on CONFIG_FIT and CONFIG_OF_LIBFDT. When enabled, fit_verity_build_cmdline() and fit_verity_apply_bootargs() are compiled into the boot path. When disabled, the functions are static inlines returning 0, so there is no code-size impact. Works with both the bootm command and BOOTSTD boot methods.