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