| ========= |
| dm-verity |
| ========= |
| |
| Device-Mapper's "verity" target provides transparent integrity checking of |
| block devices using a cryptographic digest provided by the kernel crypto API. |
| This target is read-only. |
| |
| Construction Parameters |
| ======================= |
| |
| :: |
| |
| <version> <dev> <hash_dev> |
| <data_block_size> <hash_block_size> |
| <num_data_blocks> <hash_start_block> |
| <algorithm> <digest> <salt> |
| [<#opt_params> <opt_params>] |
| |
| <version> |
| This is the type of the on-disk hash format. |
| |
| 0 is the original format used in the Chromium OS. |
| The salt is appended when hashing, digests are stored continuously and |
| the rest of the block is padded with zeroes. |
| |
| 1 is the current format that should be used for new devices. |
| The salt is prepended when hashing and each digest is |
| padded with zeroes to the power of two. |
| |
| <dev> |
| This is the device containing data, the integrity of which needs to be |
| checked. It may be specified as a path, like /dev/sdaX, or a device number, |
| <major>:<minor>. |
| |
| <hash_dev> |
| This is the device that supplies the hash tree data. It may be |
| specified similarly to the device path and may be the same device. If the |
| same device is used, the hash_start should be outside the configured |
| dm-verity device. |
| |
| <data_block_size> |
| The block size on a data device in bytes. |
| Each block corresponds to one digest on the hash device. |
| |
| <hash_block_size> |
| The size of a hash block in bytes. |
| |
| <num_data_blocks> |
| The number of data blocks on the data device. Additional blocks are |
| inaccessible. You can place hashes to the same partition as data, in this |
| case hashes are placed after <num_data_blocks>. |
| |
| <hash_start_block> |
| This is the offset, in <hash_block_size>-blocks, from the start of hash_dev |
| to the root block of the hash tree. |
| |
| <algorithm> |
| The cryptographic hash algorithm used for this device. This should |
| be the name of the algorithm, like "sha1". |
| |
| <digest> |
| The hexadecimal encoding of the cryptographic hash of the root hash block |
| and the salt. This hash should be trusted as there is no other authenticity |
| beyond this point. |
| |
| <salt> |
| The hexadecimal encoding of the salt value. |
| |
| <#opt_params> |
| Number of optional parameters. If there are no optional parameters, |
| the optional parameters section can be skipped or #opt_params can be zero. |
| Otherwise #opt_params is the number of following arguments. |
| |
| Example of optional parameters section: |
| 1 ignore_corruption |
| |
| ignore_corruption |
| Log corrupted blocks, but allow read operations to proceed normally. |
| |
| restart_on_corruption |
| Restart the system when a corrupted block is discovered. This option is |
| not compatible with ignore_corruption and requires user space support to |
| avoid restart loops. |
| |
| panic_on_corruption |
| Panic the device when a corrupted block is discovered. This option is |
| not compatible with ignore_corruption and restart_on_corruption. |
| |
| restart_on_error |
| Restart the system when an I/O error is detected. |
| This option can be combined with the restart_on_corruption option. |
| |
| panic_on_error |
| Panic the device when an I/O error is detected. This option is |
| not compatible with the restart_on_error option but can be combined |
| with the panic_on_corruption option. |
| |
| ignore_zero_blocks |
| Do not verify blocks that are expected to contain zeroes and always return |
| zeroes instead. This may be useful if the partition contains unused blocks |
| that are not guaranteed to contain zeroes. |
| |
| use_fec_from_device <fec_dev> |
| Use forward error correction (FEC) parity data from the specified device to |
| try to automatically recover from corruption and I/O errors. |
| |
| If this option is given, then <fec_roots> and <fec_blocks> must also be |
| given. <hash_block_size> must also be equal to <data_block_size>. |
| |
| <fec_dev> can be the same as <dev>, in which case <fec_start> must be |
| outside the data area. It can also be the same as <hash_dev>, in which case |
| <fec_start> must be outside the hash and optional additional metadata areas. |
| |
| If the data <dev> is encrypted, the <fec_dev> should be too. |
| |
| For more information, see `Forward error correction`_. |
| |
| fec_roots <num> |
| The number of parity bytes in each 255-byte Reed-Solomon codeword. The |
| Reed-Solomon code used will be an RS(255, k) code where k = 255 - fec_roots. |
| |
| The supported values are 2 through 24 inclusive. Higher values provide |
| stronger error correction. However, the minimum value of 2 already provides |
| strong error correction due to the use of interleaving, so 2 is the |
| recommended value for most users. fec_roots=2 corresponds to an |
| RS(255, 253) code, which has a space overhead of about 0.8%. |
| |
| fec_blocks <num> |
| The total number of <data_block_size> blocks that are error-checked using |
| FEC. This must be at least the sum of <num_data_blocks> and the number of |
| blocks needed by the hash tree. It can include additional metadata blocks, |
| which are assumed to be accessible on <hash_dev> following the hash blocks. |
| |
| Note that this is *not* the number of parity blocks. The number of parity |
| blocks is inferred from <fec_blocks>, <fec_roots>, and <data_block_size>. |
| |
| fec_start <offset> |
| This is the offset, in <data_block_size> blocks, from the start of <fec_dev> |
| to the beginning of the parity data. |
| |
| check_at_most_once |
| Verify data blocks only the first time they are read from the data device, |
| rather than every time. This reduces the overhead of dm-verity so that it |
| can be used on systems that are memory and/or CPU constrained. However, it |
| provides a reduced level of security because only offline tampering of the |
| data device's content will be detected, not online tampering. |
| |
| Hash blocks are still verified each time they are read from the hash device, |
| since verification of hash blocks is less performance critical than data |
| blocks, and a hash block will not be verified any more after all the data |
| blocks it covers have been verified anyway. |
| |
| root_hash_sig_key_desc <key_description> |
| This is the description of the USER_KEY that the kernel will lookup to get |
| the pkcs7 signature of the roothash. The pkcs7 signature is used to validate |
| the root hash during the creation of the device mapper block device. |
| Verification of roothash depends on the config DM_VERITY_VERIFY_ROOTHASH_SIG |
| being set in the kernel. The signatures are checked against the builtin |
| trusted keyring by default, or the secondary trusted keyring if |
| DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING is set. The secondary |
| trusted keyring includes by default the builtin trusted keyring, and it can |
| also gain new certificates at run time if they are signed by a certificate |
| already in the secondary trusted keyring. |
| |
| try_verify_in_tasklet |
| If verity hashes are in cache and the IO size does not exceed the limit, |
| verify data blocks in bottom half instead of workqueue. This option can |
| reduce IO latency. The size limits can be configured via |
| /sys/module/dm_verity/parameters/use_bh_bytes. The four parameters |
| correspond to limits for IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, |
| IOPRIO_CLASS_BE and IOPRIO_CLASS_IDLE in turn. |
| For example: |
| <none>,<rt>,<be>,<idle> |
| 4096,4096,4096,4096 |
| |
| Theory of operation |
| =================== |
| |
| dm-verity is meant to be set up as part of a verified boot path. This |
| may be anything ranging from a boot using tboot or trustedgrub to just |
| booting from a known-good device (like a USB drive or CD). |
| |
| When a dm-verity device is configured, it is expected that the caller |
| has been authenticated in some way (cryptographic signatures, etc). |
| After instantiation, all hashes will be verified on-demand during |
| disk access. If they cannot be verified up to the root node of the |
| tree, the root hash, then the I/O will fail. This should detect |
| tampering with any data on the device and the hash data. |
| |
| Cryptographic hashes are used to assert the integrity of the device on a |
| per-block basis. This allows for a lightweight hash computation on first read |
| into the page cache. Block hashes are stored linearly, aligned to the nearest |
| block size. |
| |
| Hash Tree |
| --------- |
| |
| Each node in the tree is a cryptographic hash. If it is a leaf node, the hash |
| of some data block on disk is calculated. If it is an intermediary node, |
| the hash of a number of child nodes is calculated. |
| |
| Each entry in the tree is a collection of neighboring nodes that fit in one |
| block. The number is determined based on block_size and the size of the |
| selected cryptographic digest algorithm. The hashes are linearly-ordered in |
| this entry and any unaligned trailing space is ignored but included when |
| calculating the parent node. |
| |
| The tree looks something like: |
| |
| alg = sha256, num_blocks = 32768, block_size = 4096 |
| |
| :: |
| |
| [ root ] |
| / . . . \ |
| [entry_0] [entry_1] |
| / . . . \ . . . \ |
| [entry_0_0] . . . [entry_0_127] . . . . [entry_1_127] |
| / ... \ / . . . \ / \ |
| blk_0 ... blk_127 blk_16256 blk_16383 blk_32640 . . . blk_32767 |
| |
| Forward error correction |
| ------------------------ |
| |
| dm-verity's optional forward error correction (FEC) support adds strong error |
| correction capabilities to dm-verity. It allows systems that would be rendered |
| inoperable by errors to continue operating, albeit with reduced performance. |
| |
| FEC uses Reed-Solomon (RS) codes that are interleaved across the entire |
| device(s), allowing long bursts of corrupt or unreadable blocks to be recovered. |
| |
| dm-verity validates any FEC-corrected block against the wanted hash before using |
| it. Therefore, FEC doesn't affect the security properties of dm-verity. |
| |
| The integration of FEC with dm-verity provides significant benefits over a |
| separate error correction layer: |
| |
| - dm-verity invokes FEC only when a block's hash doesn't match the wanted hash |
| or the block cannot be read at all. As a result, FEC doesn't add overhead to |
| the common case where no error occurs. |
| |
| - dm-verity hashes are also used to identify erasure locations for RS decoding. |
| This allows correcting twice as many errors. |
| |
| FEC uses an RS(255, k) code where k = 255 - fec_roots. fec_roots is usually 2. |
| This means that each k (usually 253) message bytes have fec_roots (usually 2) |
| bytes of parity data added to get a 255-byte codeword. (Many external sources |
| call RS codewords "blocks". Since dm-verity already uses the term "block" to |
| mean something else, we'll use the clearer term "RS codeword".) |
| |
| FEC checks fec_blocks blocks of message data in total, consisting of: |
| |
| 1. The data blocks from the data device |
| 2. The hash blocks from the hash device |
| 3. Optional additional metadata that follows the hash blocks on the hash device |
| |
| dm-verity assumes that the FEC parity data was computed as if the following |
| procedure were followed: |
| |
| 1. Concatenate the message data from the above sources. |
| 2. Zero-pad to the next multiple of k blocks. Let msg be the resulting byte |
| array, and msglen its length in bytes. |
| 3. For 0 <= i < msglen / k (for each RS codeword): |
| a. Select msg[i + j * msglen / k] for 0 <= j < k. |
| Consider these to be the 'k' message bytes of an RS codeword. |
| b. Compute the corresponding 'fec_roots' parity bytes of the RS codeword, |
| and concatenate them to the FEC parity data. |
| |
| Step 3a interleaves the RS codewords across the entire device using an |
| interleaving degree of data_block_size * ceil(fec_blocks / k). This is the |
| maximal interleaving, such that the message data consists of a region containing |
| byte 0 of all the RS codewords, then a region containing byte 1 of all the RS |
| codewords, and so on up to the region for byte 'k - 1'. Note that the number of |
| codewords is set to a multiple of data_block_size; thus, the regions are |
| block-aligned, and there is an implicit zero padding of up to 'k - 1' blocks. |
| |
| This interleaving allows long bursts of errors to be corrected. It provides |
| much stronger error correction than storage devices typically provide, while |
| keeping the space overhead low. |
| |
| The cost is slow decoding: correcting a single block usually requires reading |
| 254 extra blocks spread evenly across the device(s). However, that is |
| acceptable because dm-verity uses FEC only when there is actually an error. |
| |
| The list below contains additional details about the RS codes used by |
| dm-verity's FEC. Userspace programs that generate the parity data need to use |
| these parameters for the parity data to match exactly: |
| |
| - Field used is GF(256) |
| - Bytes are mapped to/from GF(256) elements in the natural way, where bits 0 |
| through 7 (low-order to high-order) map to the coefficients of x^0 through x^7 |
| - Field generator polynomial is x^8 + x^4 + x^3 + x^2 + 1 |
| - The codes used are systematic, BCH-view codes |
| - Primitive element alpha is 'x' |
| - First consecutive root of code generator polynomial is 'x^0' |
| |
| On-disk format |
| ============== |
| |
| The verity kernel code does not read the verity metadata on-disk header. |
| It only reads the hash blocks which directly follow the header. |
| It is expected that a user-space tool will verify the integrity of the |
| verity header. |
| |
| Alternatively, the header can be omitted and the dmsetup parameters can |
| be passed via the kernel command-line in a rooted chain of trust where |
| the command-line is verified. |
| |
| Directly following the header (and with sector number padded to the next hash |
| block boundary) are the hash blocks which are stored a depth at a time |
| (starting from the root), sorted in order of increasing index. |
| |
| The full specification of kernel parameters and on-disk metadata format |
| is available at the cryptsetup project's wiki page |
| |
| https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity |
| |
| Status |
| ====== |
| 1. V (for Valid) is returned if every check performed so far was valid. |
| If any check failed, C (for Corruption) is returned. |
| 2. Number of corrected blocks by Forward Error Correction. |
| '-' if Forward Error Correction is not enabled. |
| |
| Example |
| ======= |
| Set up a device:: |
| |
| # dmsetup create vroot --readonly --table \ |
| "0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 262144 1 sha256 "\ |
| "4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076 "\ |
| "1234000000000000000000000000000000000000000000000000000000000000" |
| |
| A command line tool veritysetup is available to compute or verify |
| the hash tree or activate the kernel device. This is available from |
| the cryptsetup upstream repository https://gitlab.com/cryptsetup/cryptsetup/ |
| (as a libcryptsetup extension). |
| |
| Create hash on the device:: |
| |
| # veritysetup format /dev/sda1 /dev/sda2 |
| ... |
| Root hash: 4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076 |
| |
| Activate the device:: |
| |
| # veritysetup create vroot /dev/sda1 /dev/sda2 \ |
| 4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076 |