1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // `dm::verity` module implements the "verity" target in the device mapper framework. Specifically, 18 // it provides `DmVerityTargetBuilder` struct which is used to construct a `DmVerityTarget` struct 19 // which is then given to `DeviceMapper` to create a mapper device. 20 21 use anyhow::{bail, Context, Result}; 22 use std::io::Write; 23 use std::mem::size_of; 24 use std::path::Path; 25 use zerocopy::AsBytes; 26 27 use crate::util::*; 28 use crate::DmTargetSpec; 29 30 // The UAPI for the verity target is here. 31 // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt 32 33 /// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using 34 /// a cryptographic digest provided by the kernel crypto API 35 pub struct DmVerityTarget(Box<[u8]>); 36 37 /// Version of the verity target spec. 38 pub enum DmVerityVersion { 39 /// Only `1` is supported. 40 V1, 41 } 42 43 /// The hash algorithm to use. SHA256 and SHA512 are supported. 44 #[allow(dead_code)] 45 pub enum DmVerityHashAlgorithm { 46 /// sha with 256 bit hash 47 SHA256, 48 /// sha with 512 bit hash 49 SHA512, 50 } 51 52 /// A builder that constructs `DmVerityTarget` struct. 53 pub struct DmVerityTargetBuilder<'a> { 54 version: DmVerityVersion, 55 data_device: Option<&'a Path>, 56 data_size: u64, 57 hash_device: Option<&'a Path>, 58 hash_algorithm: DmVerityHashAlgorithm, 59 root_digest: Option<&'a [u8]>, 60 salt: Option<&'a [u8]>, 61 } 62 63 impl DmVerityTarget { 64 /// flatten into slice as_slice(&self) -> &[u8]65 pub fn as_slice(&self) -> &[u8] { 66 self.0.as_ref() 67 } 68 } 69 70 impl<'a> Default for DmVerityTargetBuilder<'a> { default() -> Self71 fn default() -> Self { 72 DmVerityTargetBuilder { 73 version: DmVerityVersion::V1, 74 data_device: None, 75 data_size: 0, 76 hash_device: None, 77 hash_algorithm: DmVerityHashAlgorithm::SHA256, 78 root_digest: None, 79 salt: None, 80 } 81 } 82 } 83 84 impl<'a> DmVerityTargetBuilder<'a> { 85 /// Sets the device that will be used as the data device (i.e. providing actual data). data_device(&mut self, p: &'a Path, size: u64) -> &mut Self86 pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self { 87 self.data_device = Some(p); 88 self.data_size = size; 89 self 90 } 91 92 /// Sets the device that provides the merkle tree. hash_device(&mut self, p: &'a Path) -> &mut Self93 pub fn hash_device(&mut self, p: &'a Path) -> &mut Self { 94 self.hash_device = Some(p); 95 self 96 } 97 98 /// Sets the hash algorithm that the merkle tree is using. hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self99 pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self { 100 self.hash_algorithm = algo; 101 self 102 } 103 104 /// Sets the root digest of the merkle tree. The format is hexadecimal string. root_digest(&mut self, digest: &'a [u8]) -> &mut Self105 pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self { 106 self.root_digest = Some(digest); 107 self 108 } 109 110 /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees 111 /// created following the APK signature scheme V4. salt(&mut self, salt: &'a [u8]) -> &mut Self112 pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self { 113 self.salt = Some(salt); 114 self 115 } 116 117 /// Constructs a `DmVerityTarget`. build(&self) -> Result<DmVerityTarget>118 pub fn build(&self) -> Result<DmVerityTarget> { 119 // The `DmVerityTarget` struct actually is a flattened data consisting of a header and 120 // body. The format of the header is `dm_target_spec` as defined in 121 // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is 122 // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt 123 // 124 // Step 1: check the validity of the inputs and extra additional data (e.g. block size) 125 // from them. 126 let version = match self.version { 127 DmVerityVersion::V1 => 1, 128 }; 129 130 let data_device_path = self 131 .data_device 132 .context("data device is not set")? 133 .to_str() 134 .context("data device path is not encoded in utf8")?; 135 let stat = fstat(self.data_device.unwrap())?; // safe; checked just above 136 let data_block_size = stat.st_blksize as u64; 137 let data_size = self.data_size; 138 let num_data_blocks = data_size / data_block_size; 139 140 let hash_device_path = self 141 .hash_device 142 .context("hash device is not set")? 143 .to_str() 144 .context("hash device path is not encoded in utf8")?; 145 let stat = fstat(self.data_device.unwrap())?; // safe; checked just above 146 let hash_block_size = stat.st_blksize; 147 148 let hash_algorithm = match self.hash_algorithm { 149 DmVerityHashAlgorithm::SHA256 => "sha256", 150 DmVerityHashAlgorithm::SHA512 => "sha512", 151 }; 152 153 let root_digest = if let Some(root_digest) = self.root_digest { 154 hex::encode(root_digest) 155 } else { 156 bail!("root digest is not set") 157 }; 158 159 let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() { 160 "-".to_string() // Note. It's not an empty string! 161 } else { 162 hex::encode(self.salt.unwrap()) 163 }; 164 165 // Step2: serialize the information according to the spec, which is ... 166 // DmTargetSpec{...} 167 // <version> <dev> <hash_dev> 168 // <data_block_size> <hash_block_size> 169 // <num_data_blocks> <hash_start_block> 170 // <algorithm> <digest> <salt> 171 // [<#opt_params> <opt_params>] 172 // null terminator 173 174 // TODO(jiyong): support the optional parameters... if needed. 175 let mut body = String::new(); 176 use std::fmt::Write; 177 write!(&mut body, "{} ", version)?; 178 write!(&mut body, "{} ", data_device_path)?; 179 write!(&mut body, "{} ", hash_device_path)?; 180 write!(&mut body, "{} ", data_block_size)?; 181 write!(&mut body, "{} ", hash_block_size)?; 182 write!(&mut body, "{} ", num_data_blocks)?; 183 write!(&mut body, "{} ", 0)?; // hash_start_block 184 write!(&mut body, "{} ", hash_algorithm)?; 185 write!(&mut body, "{} ", root_digest)?; 186 write!(&mut body, "{}", salt)?; 187 write!(&mut body, "\0")?; // null terminator 188 189 let size = size_of::<DmTargetSpec>() + body.len(); 190 let aligned_size = (size + 7) & !7; // align to 8 byte boundaries 191 let padding = aligned_size - size; 192 let mut header = DmTargetSpec::new("verity")?; 193 header.sector_start = 0; 194 header.length = data_size / 512; // number of 512-byte sectors 195 header.next = aligned_size as u32; 196 197 let mut buf = Vec::with_capacity(aligned_size); 198 buf.write_all(header.as_bytes())?; 199 buf.write_all(body.as_bytes())?; 200 buf.write_all(vec![0; padding].as_slice())?; 201 Ok(DmVerityTarget(buf.into_boxed_slice())) 202 } 203 } 204