
If you're concerned about the integrity of your system, dm-verity is a key component of the Linux ecosystem for reliable booting and storage tampering detection. Originally part of the kernel's device-mapper, it now forms the basis for verified boot processes in Android, OpenWrt, and distributions seeking enhanced security.
Far from being an abstract concept, dm-verity is configured and used with real-world tools like veritysetup and systemd-veritysetup . It validates blocks on the fly using hash trees and can react to corruption with policies ranging from logging the event to restarting or blocking the system. Let's take a thorough look, leaving no stone unturned.
What is dm-verity and why you might care
`dm-verity` is a kernel target of `device-mapper` that verifies the integrity of a block device as data is read . It works by calculating and checking the hashes of each block (usually 4K) against a precomputed check tree, typically using SHA-256.
This design prevents files from being silently modified between reboots or during execution . It is key to extending the boot chain of trust to the operating system, limiting malware persistence, strengthening security policies, and providing assurance to encryption mechanisms and MACs during boot.
In Android (since version 4.4) and Linux in general, trust is anchored in the root hash of the tree , which is signed and validated with a public key residing in a protected location (for example, in the boot partition or in a UKI signed with Secure Boot). Breaking any block would require breaking the underlying cryptographic hash.
Verification is performed on a block basis and on demand: the added latency is minimal compared to the I/O cost . If a check fails, the kernel returns an I/O error, and the filesystem appears corrupted, which is expected when the data is untrusted. Applications can then decide whether to continue or not based on their fault tolerance.
How the verification tree works internally
The verification tree is built in layers. Layer 0 is the raw device data, divided into 4K blocks ; a SHA-256 hash (with salt) is calculated for each block. These hashes are concatenated to form layer 1. In turn, layer 1 is grouped into blocks and rehashed to form layer 2, and so on until everything fits into a single block: that block, when hashed, produces the root hash.
If any layer does not exactly fill a block, it is padded with zeros until it reaches 4K to avoid ambiguity. The total size of the tree depends on the size of the verified partition; in practice, it is usually less than about 30 MB for typical system partitions.
The general process is: choose a random salt, chop it into 4K blocks, calculate SHA-256 with the salt per block , concatenate to form levels, pad with zeros at block boundaries, and repeat with the previous level until only a single root hash remains. This root hash, along with the salt used, feeds the dm-verity table and the signature.
Disk format versions and algorithm
The format of hash blocks on disk has a version. Version 0 was the original one used in Chromium OS : the salt is added at the end during hashing, the digests are stored continuously, and the rest of the block is padded with zeros.
Version 1 is recommended for new devices : the salt is added to the hash, and each digest is padded with zeros up to powers of two, improving alignment and robustness. The dm-verity table also specifies the algorithm (e.g., sha1 or sha256), although sha256 is used for current security.
dm-verity table and essential parameters
The dm-verity destination table describes where the data is located, where the hash tree is, and how to verify it . Typical table fields:
- giant: device with the data to be verified (path type /dev/sdXN or greater:lesser).
- hash_dev: device with the hash tree (can be the same; if so, hash_start must be outside the checked range).
- data_block_size: data block size in bytes (e.g. 4096).
- hash_block_size: hash block size in bytes.
- num_data_blocks: number of verifiable data blocks.
- hash_start_block: offset (in hash_block_size blocks) to the root block of the tree.
- algorithm: hash algorithm (e.g. sha256).
- digest: hexadecimal encoding of the root block hash (including salt according to format version); this value is the one to trust.
- salt: hexadecimal salt.
In addition, there are very useful optional parameters for adjusting the behavior:
- ignore_corruption: Records corrupt blocks, but allows reading to continue.
- restart_on_corruption: restart on corruption detection (not compatible with ignore_corruption and requires user-space support to avoid loops).
- panic_on_corruption: : causes panic when detecting corruption (not compatible with previous versions).
- restart_on_error y panic_on_error: same reactions but for I/O errors.
- ignore_zero_blocks: does not check blocks that are expected as zeros and returns zeros.
- use_fec_from_device + fec_roots + fec_blocks + fec_start: Enable Reed–Solomon (FEC) to recover data when verification fails; the data, hash, and FEC areas must not overlap, and the block sizes must match.
- check_at_most_once: Checks each block of data only the first time it is read (reduces overhead at the cost of security in live attacks).
- root_hash_sig_key_desc: Reference to a key in the keyring to validate a PKCS7 signature of the root hash when creating the mapping (requires appropriate kernel configuration and trusted keyrings).
- try_verify_in_tasklet: If hashes are cached and I/O size allows, checks bottom-half to reduce latency; adjusted with /sys/module/dm_verity/parameters/use_bh_bytes per I/O class.
Signature, metadata and trust anchoring
For dm-verity to be reliable, the root hash must be trusted and, typically, signed . In classic Android, a public key is included in the boot partition, which the manufacturer verifies externally; this validates the root hash signature and ensures that the system partition has not been altered.
Verity metadata adds structure and version control. The metadata block includes a magic number 0xb001b001 (bytes b0 01 b0 01), version (currently 0), the table signature in PKCS1.5 (typically 256 bytes for RSA-2048), the table length, the table itself, and padding with zeros up to 32K.
In Android implementations, verification relies on fs_mgr and fstab : adding the check mark to the corresponding entry and placing the key in /boot/verity_key. If the magic number doesn't appear where it should, the verification stops to avoid verifying incorrect information.
Start operation verified
The protection resides in the kernel: if it's compromised before the kernel boots, the attacker retains control . That's why manufacturers typically validate each stage rigorously: a key burned into the device verifies the first bootloader, which verifies the next, the app bootloader, and finally, the kernel.
With the kernel verified, dm-verity is activated when the verified block device is mounted . Instead of hashing the entire device (which would be slow and energy-intensive), it verifies each block as it is accessed. A failure results in I/O errors, and services and apps react according to their tolerance: either continue without the data or fail completely.
Forward Error Correction (FEC)
Since Android 7.0, FEC (Reed-Solomon) has been incorporated with interleaving techniques to reduce space usage and increase the ability to recover damaged blocks. This coexists with dm-verity: if a check fails, the subsystem can attempt correction before declaring the block unrecoverable.
Performance and optimization
To reduce the impact: enable SHA-2 acceleration via NEON on ARMv7 and SHA-2 extensions on ARMv8 from the kernel. Adjust read-ahead and prefetch_cluster parameters for your hardware; block verification usually adds little compared to the I/O cost, but these adjustments make a difference.
Getting started on Linux (systemd, veritysetup) and Android
On a modern Linux system with systemd, dm-verity enables a read-only root account verified using veritysetup (part of cryptsetup), systemd-veritysetup.generator, and systemd-veritysetup@.service. It is recommended to use Secure Boot and a signed UKI (unified kernel image), although these are not strictly required.
Preparation and recommended partitioning
Part of a functional and optimized system. Reserve a volume for the hash tree (8–10% of the root size is usually sufficient) and consider separating /home and /var if you need to write to them. A typical scheme includes: ESP (for the bootloader), XBOOTLDR (for UKIs), root (with or without encryption), VERITY partition, and optionally /home and /var.
As a root, EROFS is a very interesting alternative to ext4 or squashfs : it is read-only by design, with very good performance on flash/SSD, lz4 compression by default, and widely used in Android phones with dm-verity.
Files that must be writable
With root access, some programs expect to write to /etc or during init . You can move the necessary files to /var/etc and symbolically link them (e.g., NetworkManager connections to /etc/NetworkManager/system-connections). Note that systemd-journald requires /etc/machine-id to exist in the root directory (not just a symlink) to avoid breaking early startups.
To discover what changes at runtime, use `dracut-overlayroot` : it overlays a tmpfs file over the root directory, and everything written appears in `/run/overlayroot/u`. Add the module to `/usr/lib/dracut/modules.d/`, include `overlayroot` in `dracut`, and set `overlayroot=1` in the kernel line; this will show you what to migrate to `/var`.
Useful examples: pacman and NetworkManager
In Arch, it's advisable to move the pacman database to /usr/lib/pacman so that the rootfs always reflects the installed packages. Then, redirect the cache to /var/lib/pacman and link it. To change the mirrorlist without affecting the root directory, move it to /var/etc and link it in the same way.
With NetworkManager, move system-connections to /var/etc/NetworkManager and link from /etc/NetworkManager/system-connections. This keeps the root immutable and the configuration live where it needs to be writable.
Construction of verity and testing
From a live environment with everything working perfectly and set up in RO, create the root tree and root hash with `veritysetup format` : when executed, it prints the Root Hash line which you can save in `roothash.txt`. Open for testing with `veritysetup open root-device root verity-device $(cat roothash.txt)` and mount `/dev/mapper/root`.
If you prefer, first generate the tree to a file (verity.bin) and then write it to the VERITY partition. The resulting set includes: the root image, the verity tree, and the root hash that you will anchor at boot time.
Configure the kernel line
Add these parameters: systemd.verity=1 , roothash=roothash_contents.txt, systemd.verity_root_data=ROOT-PATH (for example, LABEL=OS), and systemd.verity_root_hash=VERITY-PATH (for example, LABEL=VERITY). Set systemd.verity_root_options to restart-on-corruption or panic-on-corruption for strict policies.
Other recommended options: ro (if you don't use EROFS/squashfs), rd.emergency=reboot and rd.shell=0 (to prevent unauthorized shells if booting fails), and lockdown=confidentiality to protect kernel memory from access.
Additional partitions with verity
Not just the root partition: you can define other mappings in /etc/veritytab and systemd-veritysetup@.service will assemble them at boot. Remember: it's easier to remount a non-root partition in rw, and a root user could disable verity on those partitions, so the security value there is lower.
Security: Secure Boot, UKI and signed modules
dm-verity is not a silver bullet. It signs the UKI and enables Secure Boot with its own keys to prevent anyone from replacing kernel/initramfs/cmdline (which include the roothash). Tools like sbupdate-git or sbctl help maintain signed images and an intact boot chain.
If you enable kernel lockdown or module signature verification, DKMS or out-of-tree modules must be signed or they will not load. Consider using a custom kernel with signature support for your workflow (see signed kernel modules).
Encryption, TPM and metering
dm-verity protects integrity, not confidentiality . You can leave the root unencrypted if it doesn't contain secrets and the boot chain is protected. If you use keyfiles from the root to unlock other volumes, then it's advisable to encrypt it.
With TPM 2.0, systemd-cryptenroll allows you to bind keys to PCRs 0, 1, 5, and 7 (firmware, options, GPT, secure boot status). Add `rd.luks.options=UUID_of_LUKS=tpm2-device=auto` and ensure you include TPM2 support in the initramfs. systemd-boot measures the kernel.efi file in PCR4, which is useful for invalidating keys if the UKI or its command line changes.
Updates and deployment models
A verified read-only root is not updated using the traditional package manager . Ideally, new images should be built using tools like the Yocto project and then deployed. Systemd provides `systemd-sysupdate` and `systemd-repart` for robustly downloading and flashing images.
Another strategy is an A/B swap : you maintain two roots and two verities. You copy the active one to the inactive one, apply changes, and rebuild the verity. On the next boot, you switch over. If you're using a UKI, remember to update the root hash in the command line or rebuild the signed UKI.
For optional persistence, use OverlayFS on the root verified with upper in tmpfs or disk. You can also pass systemd.volatile=overlay for temporary persistence. Flatpak makes it easy to install apps in /var and /home without touching /.
There are packages that automate this process (e.g., verity-squash-root in the AUR) by building a root squashfs and signing the roothash with the kernel and initramfs , allowing you to choose between persistent or ephemeral mode and keep the latest rootfs as a backup. Note: Adding persistence to a verified root has narrow use cases; try persisting app data on separate partitions.
Android: system-as-root, AVB and vendor overlays
Starting with Android 10, rootfs is no longer stored in the ramdisk and is integrated with system.img (system-as-root). Devices that launch with Android 10 always use this scheme and require a ramdisk for dm-linear. BOARD_BUILD_SYSTEM_ROOT_IMAGE is set to false in this generation to distinguish between using a ramdisk and directly activating system.img.
Android 10 introduces dynamic partitioning and a first-stage init process that activates the logical system partition; the kernel no longer mounts it directly. System-only OTAs require a system-as-root design, mandatory on Android 10 devices.
In non-A/B configurations, keep recovery separate from boot . Unlike A/B configurations, there is no boot_a/boot_b backup, so removing recovery in non-A/B configurations can leave you without recovery mode if a boot update fails.
The kernel mounts system.img in / with verity using two methods: vboot 1.0 (patches for the kernel to parse Android metadata in /system and derive dm-verity parameters; the cmdline includes root=/dev/dm-0, skip_initramfs and init=/init with dm=…) or vboot 2.0/AVB , where the bootloader integrates libavb, reads the hashtree descriptor (in vbmeta or system), constructs the parameters and passes them to the kernel in the cmdline, with FEC support and flags such as restart_on_corruption.
With system-as-root, do not use BOARD_ROOT_EXTRA_FOLDERS for device-specific root folders: they will disappear when flashing a GSI. Define specific mounts under /mnt/vendor/<dot>, which fs_mgr creates automatically, and reference them in the device tree's fstab file.
Android allows a vendor overlay from /product/vendor_overlay/<version> : init will mount subdirectories in /vendor that meet the SELinux context requirements and the existence of /vendor/<overlay_dir>. This requires CONFIG_OVERLAY_FS=yy, and in older kernels, the override_creds=off patch.
Typical implementation: Install pre-compiled files in device/<vendor>/<target>/vendor_overlay/<version> , add them to PRODUCT_COPY_FILES using find-copy-subdir-files towards $(TARGET_COPY_OUT_PRODUCT)/vendor_overlay, define contexts in file_contexts for etc and app (e.g., vendor_configs_file and vendor_app_file), and enable mounton on those contexts in init.te. Test with atest vfs_mgr_vendor_overlay_test in userdebug.
Troubleshooting: dm-verity corruption message on Android
On devices with A/B slots, changing slots or flashing vbmeta/boot without root hash compatibility can trigger the warning: "dm-verity corruption, your device is not trusted." Commands like `fastboot flash --disable-verity --disable-verification vbmeta vbmeta.img` disable verification, but leave the system without integrity guarantees.
Some bootloaders support `fastboot oem disable_dm_verity` and its opposite `enable_dm_verity`. It works on some models, but not on others; and it may require kernel/magisk with adjusted flags. Use it at your own risk: the prudent thing to do is align `boot`, `vbmeta`, and `system` , sign or regenerate the tree, and ensure that the expected root hash matches the configured one.
If you can continue pressing the power button after the warning, the system will boot, but your chain of trust will no longer be intact . To remove the message without sacrificing security, restore the original signed images or rebuild/verify vbmeta with the correct hashtree, instead of disabling verity.
i.MX and OpenWrt platforms
In i.MX6 (e.g., sabresd), configure the kernel with DM_VERITY and FEC support , generate the tree with veritysetup, reliably store the root hash, and pass the appropriate parameters in the cmdline, or integrate via initramfs with systemd-veritysetup. If you're not using dm-crypt, you don't need CAAM for verity; the focus is on integrity.
In OpenWrt and embedded Linux systems with OpenEmbedded , efforts are underway to integrate dm-verity and SELinux (Bootlin's work has been reviewed with the intention of incorporating support). This is a natural fit: routers and network equipment benefit from an immutable, verified, and hardened root with MAC addresses.
Manual tree and metadata construction (detailed view)
cryptsetup can generate the tree for you, but if you prefer to understand the format, the compact table line definition includes: mapping name, data device, data block sizes and hash, image size in blocks , hash_start position (image in blocks + 8 if concatenated), root hash, and exit. After generating the concatenated layers (from top to bottom, excluding layer 0), you write the tree to disk.
To package everything, create the dm-verity table, sign it (typical RSA-2048), and group the signature and table into metadata with a versioned header and magic number. Then, concatenate the system image, verity metadata, and hash tree. In fstab, mark fs_mgr with verify and place the public key in /boot/verity_key to validate the signature.
Optimize with SHA-2 acceleration of your CPU and adjust read-ahead/prefetch_cluster. On ARM hardware, NEON SHA-2 (ARMv7) and SHA-2 extensions (ARMv8) significantly reduce the verification load.
In any deployment, remember that the root hash value must be protected : either by compiling it into a signed UKI, storing it in the signed boot partition, or validating it with the bootloader using AVB. Everything that happens after that point inherits that trust.
With all of the above in place, dm-verity becomes a solid foundation for immutable, mobile, and embedded systems , supporting transactional updates, configuration overlays, and a modern security model that reduces the attack surface and prevents persistence without sacrificing performance.


