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` module implements part of the `device-mapper` ioctl interfaces. It currently supports
18 // creation and deletion of the mapper device. It doesn't support other operations like querying
19 // the status of the mapper device. And there's no plan to extend the support unless it is
20 // required.
21 //
22 // Why in-house development? [`devicemapper`](https://crates.io/crates/devicemapper) is a public
23 // Rust implementation of the device mapper APIs. However, it doesn't provide any abstraction for
24 // the target-specific tables. User has to manually craft the table. Ironically, the library
25 // provides a lot of APIs for the features that are not required for `apkdmverity` such as listing
26 // the device mapper block devices that are currently listed in the kernel. Size is an important
27 // criteria for Microdroid.
28 
29 //! A library to create device mapper spec & issue ioctls.
30 
31 #![allow(missing_docs)]
32 #![cfg_attr(test, allow(unused))]
33 
34 use anyhow::{Context, Result};
35 use std::fs::{File, OpenOptions};
36 use std::io::Write;
37 use std::mem::size_of;
38 use std::os::unix::io::AsRawFd;
39 use std::path::{Path, PathBuf};
40 use zerocopy::AsBytes;
41 use zerocopy::FromZeroes;
42 
43 /// Exposes DmCryptTarget & related builder
44 pub mod crypt;
45 /// Expose util functions
46 pub mod util;
47 /// Exposes the DmVerityTarget & related builder
48 pub mod verity;
49 // Expose loopdevice
50 pub mod loopdevice;
51 
52 mod sys;
53 use crypt::DmCryptTarget;
54 use sys::*;
55 use util::*;
56 use verity::DmVerityTarget;
57 
58 nix::ioctl_readwrite!(_dm_dev_create, DM_IOCTL, Cmd::DM_DEV_CREATE, DmIoctl);
59 nix::ioctl_readwrite!(_dm_dev_suspend, DM_IOCTL, Cmd::DM_DEV_SUSPEND, DmIoctl);
60 nix::ioctl_readwrite!(_dm_table_load, DM_IOCTL, Cmd::DM_TABLE_LOAD, DmIoctl);
61 nix::ioctl_readwrite!(_dm_dev_remove, DM_IOCTL, Cmd::DM_DEV_REMOVE, DmIoctl);
62 
63 /// Create a new (mapper) device
dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32>64 fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
65     // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
66     // state of this process in any way.
67     Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
68 }
69 
dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32>70 fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
71     // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
72     // state of this process in any way.
73     Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
74 }
75 
dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32>76 fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
77     // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
78     // state of this process in any way.
79     Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
80 }
81 
dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32>82 fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
83     // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
84     // state of this process in any way.
85     Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
86 }
87 
88 // `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
89 // ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
90 #[repr(C)]
91 #[derive(Copy, Clone, AsBytes, FromZeroes)]
92 struct DmTargetSpec {
93     sector_start: u64,
94     length: u64, // number of 512 sectors
95     status: i32,
96     next: u32,
97     target_type: [u8; DM_MAX_TYPE_NAME],
98 }
99 
100 impl DmTargetSpec {
new(target_type: &str) -> Result<Self>101     fn new(target_type: &str) -> Result<Self> {
102         let mut spec = Self::new_zeroed();
103         spec.target_type.as_mut().write_all(target_type.as_bytes())?;
104         Ok(spec)
105     }
106 }
107 
108 impl DmIoctl {
new(name: &str) -> Result<DmIoctl>109     fn new(name: &str) -> Result<DmIoctl> {
110         let mut data: Self = Self::new_zeroed();
111         data.version[0] = DM_VERSION_MAJOR;
112         data.version[1] = DM_VERSION_MINOR;
113         data.version[2] = DM_VERSION_PATCHLEVEL;
114         data.data_size = size_of::<Self>() as u32;
115         data.data_start = 0;
116         data.name.as_mut().write_all(name.as_bytes())?;
117         Ok(data)
118     }
119 
set_uuid(&mut self, uuid: &str) -> Result<()>120     fn set_uuid(&mut self, uuid: &str) -> Result<()> {
121         let mut dst = self.uuid.as_mut();
122         dst.fill(0);
123         dst.write_all(uuid.as_bytes())?;
124         Ok(())
125     }
126 }
127 
128 /// `DeviceMapper` is the entry point for the device mapper framework. It essentially is a file
129 /// handle to "/dev/mapper/control".
130 pub struct DeviceMapper(File);
131 
132 #[cfg(not(target_os = "android"))]
133 const MAPPER_CONTROL: &str = "/dev/mapper/control";
134 #[cfg(not(target_os = "android"))]
135 const MAPPER_DEV_ROOT: &str = "/dev/mapper";
136 
137 #[cfg(target_os = "android")]
138 const MAPPER_CONTROL: &str = "/dev/device-mapper";
139 #[cfg(target_os = "android")]
140 const MAPPER_DEV_ROOT: &str = "/dev/block/mapper";
141 
142 impl DeviceMapper {
143     /// Constructs a new `DeviceMapper` entrypoint. This is essentially the same as opening
144     /// "/dev/mapper/control".
new() -> Result<DeviceMapper>145     pub fn new() -> Result<DeviceMapper> {
146         let f = OpenOptions::new()
147             .read(true)
148             .write(true)
149             .open(MAPPER_CONTROL)
150             .context(format!("failed to open {}", MAPPER_CONTROL))?;
151         Ok(DeviceMapper(f))
152     }
153 
154     /// Creates a (crypt) device and configure it according to the `target` specification.
155     /// The path to the generated device is "/dev/mapper/<name>".
create_crypt_device(&self, name: &str, target: &DmCryptTarget) -> Result<PathBuf>156     pub fn create_crypt_device(&self, name: &str, target: &DmCryptTarget) -> Result<PathBuf> {
157         self.create_device(name, target.as_slice(), uuid("crypto".as_bytes())?, true)
158     }
159 
160     /// Creates a (verity) device and configure it according to the `target` specification.
161     /// The path to the generated device is "/dev/mapper/<name>".
create_verity_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf>162     pub fn create_verity_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf> {
163         self.create_device(name, target.as_slice(), uuid("apkver".as_bytes())?, false)
164     }
165 
166     /// Removes a mapper device.
delete_device_deferred(&self, name: &str) -> Result<()>167     pub fn delete_device_deferred(&self, name: &str) -> Result<()> {
168         let mut data = DmIoctl::new(name)?;
169         data.flags |= Flag::DM_DEFERRED_REMOVE;
170         dm_dev_remove(self, &mut data)
171             .context(format!("failed to remove device with name {}", &name))?;
172         Ok(())
173     }
174 
create_device( &self, name: &str, target: &[u8], uid: String, writable: bool, ) -> Result<PathBuf>175     fn create_device(
176         &self,
177         name: &str,
178         target: &[u8],
179         uid: String,
180         writable: bool,
181     ) -> Result<PathBuf> {
182         // Step 1: create an empty device
183         let mut data = DmIoctl::new(name)?;
184         data.set_uuid(&uid)?;
185         dm_dev_create(self, &mut data)
186             .context(format!("failed to create an empty device with name {}", &name))?;
187 
188         // Step 2: load table onto the device
189         let payload_size = size_of::<DmIoctl>() + target.len();
190 
191         let mut data = DmIoctl::new(name)?;
192         data.data_size = payload_size as u32;
193         data.data_start = size_of::<DmIoctl>() as u32;
194         data.target_count = 1;
195 
196         if !writable {
197             data.flags |= Flag::DM_READONLY_FLAG;
198         }
199 
200         let mut payload = Vec::with_capacity(payload_size);
201         payload.extend_from_slice(data.as_bytes());
202         payload.extend_from_slice(target);
203         dm_table_load(self, payload.as_mut_ptr() as *mut DmIoctl)
204             .context("failed to load table")?;
205 
206         // Step 3: activate the device (note: the term 'suspend' might be misleading, but it
207         // actually activates the table. See include/uapi/linux/dm-ioctl.h
208         let mut data = DmIoctl::new(name)?;
209         dm_dev_suspend(self, &mut data).context("failed to activate")?;
210 
211         // Step 4: wait unti the device is created and return the device path
212         let path = Path::new(MAPPER_DEV_ROOT).join(name);
213         wait_for_path(&path)?;
214         Ok(path)
215     }
216 }
217 
218 /// Used to derive a UUID that uniquely identifies a device mapper device when creating it.
uuid(node_id: &[u8]) -> Result<String>219 fn uuid(node_id: &[u8]) -> Result<String> {
220     use std::time::{SystemTime, UNIX_EPOCH};
221     use uuid::v1::{Context, Timestamp};
222     use uuid::Uuid;
223 
224     let context = Context::new(0);
225     let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
226     let ts = Timestamp::from_unix(context, now.as_secs(), now.subsec_nanos());
227     let uuid = Uuid::new_v1(ts, node_id.try_into()?);
228     Ok(String::from(uuid.hyphenated().encode_lower(&mut Uuid::encode_buffer())))
229 }
230 
231 #[cfg(test)]
232 rdroidtest::test_main!();
233 
234 #[cfg(test)]
235 mod tests {
236     use super::*;
237     use crypt::{CipherType, DmCryptTargetBuilder};
238     use rdroidtest::{ignore_if, rdroidtest};
239     use rustutils::system_properties;
240     use std::fs::{read, File, OpenOptions};
241     use std::io::Write;
242 
243     // Just a logical set of keys to make testing easy. This has no real meaning.
244     struct KeySet<'a> {
245         cipher: CipherType,
246         key: &'a [u8],
247         different_key: &'a [u8],
248     }
249 
250     const KEY_SET_XTS: KeySet = KeySet {
251         cipher: CipherType::AES256XTS,
252         key: b"sixtyfourbyteslongsentencearerarebutletsgiveitatrycantbethathard",
253         different_key: b"drahtahtebtnacyrtatievigsteltuberareraecnetnesgnolsetybruofytxis",
254     };
255     const KEY_SET_HCTR2: KeySet = KeySet {
256         cipher: CipherType::AES256HCTR2,
257         key: b"thirtytwobyteslongreallylongword",
258         different_key: b"drowgnolyllaergnolsetybowtytriht",
259     };
260 
261     // Create a file in given temp directory with given size
prepare_tmpfile(test_dir: &Path, filename: &str, sz: u64) -> PathBuf262     fn prepare_tmpfile(test_dir: &Path, filename: &str, sz: u64) -> PathBuf {
263         let filepath = test_dir.join(filename);
264         let f = File::create(&filepath).unwrap();
265         f.set_len(sz).unwrap();
266         filepath
267     }
268 
write_to_dev(path: &Path, data: &[u8])269     fn write_to_dev(path: &Path, data: &[u8]) {
270         let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
271         f.write_all(data).unwrap();
272     }
273 
274     // TODO(b/250880499): delete_device() doesn't really delete it even without DM_DEFERRED_REMOVE.
275     // Hence, we have to create a new device with a different name for each test. Retrying
276     // the test on same machine without reboot will also fail.
delete_device(dm: &DeviceMapper, name: &str) -> Result<()>277     fn delete_device(dm: &DeviceMapper, name: &str) -> Result<()> {
278         dm.delete_device_deferred(name)?;
279         wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(name))?;
280         Ok(())
281     }
282 
is_hctr2_supported() -> bool283     fn is_hctr2_supported() -> bool {
284         // hctr2 is NOT enabled in kernel 5.10 or lower. We run Microdroid tests on kernel versions
285         // 5.10 or above & therefore,  we don't really care to skip test on other versions.
286         if let Some(version) = system_properties::read("ro.kernel.version")
287             .expect("Unable to read system property ro.kernel.version")
288         {
289             version != "5.10"
290         } else {
291             panic!("Could not read property: kernel.version!!");
292         }
293     }
294 
295     #[rdroidtest]
mapping_again_keeps_data_xts()296     fn mapping_again_keeps_data_xts() {
297         mapping_again_keeps_data(&KEY_SET_XTS, "name1");
298     }
299 
300     #[rdroidtest]
301     #[ignore_if(!is_hctr2_supported())]
mapping_again_keeps_data_hctr2()302     fn mapping_again_keeps_data_hctr2() {
303         mapping_again_keeps_data(&KEY_SET_HCTR2, "name2");
304     }
305 
306     #[rdroidtest]
data_inaccessible_with_diff_key_xts()307     fn data_inaccessible_with_diff_key_xts() {
308         data_inaccessible_with_diff_key(&KEY_SET_XTS, "name3");
309     }
310 
311     #[rdroidtest]
312     #[ignore_if(!is_hctr2_supported())]
data_inaccessible_with_diff_key_hctr2()313     fn data_inaccessible_with_diff_key_hctr2() {
314         data_inaccessible_with_diff_key(&KEY_SET_HCTR2, "name4");
315     }
316 
mapping_again_keeps_data(keyset: &KeySet, device: &str)317     fn mapping_again_keeps_data(keyset: &KeySet, device: &str) {
318         // This test creates 2 different crypt devices using same key backed by same data_device
319         // -> Write data on dev1 -> Check the data is visible & same on dev2
320         let dm = DeviceMapper::new().unwrap();
321         let inputimg = include_bytes!("../testdata/rand8k");
322         let sz = inputimg.len() as u64;
323 
324         let test_dir = tempfile::TempDir::new().unwrap();
325         let backing_file = prepare_tmpfile(test_dir.path(), "storage", sz);
326         let data_device = loopdevice::attach(
327             backing_file,
328             0,
329             sz,
330             /* direct_io */ true,
331             /* writable */ true,
332         )
333         .unwrap();
334         let device_diff = device.to_owned() + "_diff";
335 
336         scopeguard::defer! {
337             loopdevice::detach(&data_device).unwrap();
338             let _ignored1 = delete_device(&dm, device);
339             let _ignored2 = delete_device(&dm, &device_diff);
340         }
341 
342         let target = DmCryptTargetBuilder::default()
343             .data_device(&data_device, sz)
344             .cipher(keyset.cipher)
345             .key(keyset.key)
346             .build()
347             .unwrap();
348 
349         let mut crypt_device = dm.create_crypt_device(device, &target).unwrap();
350         write_to_dev(&crypt_device, inputimg);
351 
352         // Recreate another device using same target spec & check if the content is the same
353         crypt_device = dm.create_crypt_device(&device_diff, &target).unwrap();
354 
355         let crypt = read(crypt_device).unwrap();
356         assert_eq!(inputimg.len(), crypt.len()); // fail early if the size doesn't match
357         assert_eq!(inputimg, crypt.as_slice());
358     }
359 
data_inaccessible_with_diff_key(keyset: &KeySet, device: &str)360     fn data_inaccessible_with_diff_key(keyset: &KeySet, device: &str) {
361         // This test creates 2 different crypt devices using different keys backed
362         // by same data_device -> Write data on dev1 -> Check the data is visible but not the same
363         // on dev2
364         let dm = DeviceMapper::new().unwrap();
365         let inputimg = include_bytes!("../testdata/rand8k");
366         let sz = inputimg.len() as u64;
367 
368         let test_dir = tempfile::TempDir::new().unwrap();
369         let backing_file = prepare_tmpfile(test_dir.path(), "storage", sz);
370         let data_device = loopdevice::attach(
371             backing_file,
372             0,
373             sz,
374             /* direct_io */ true,
375             /* writable */ true,
376         )
377         .unwrap();
378         let device_diff = device.to_owned() + "_diff";
379         scopeguard::defer! {
380             loopdevice::detach(&data_device).unwrap();
381             let _ignored1 = delete_device(&dm, device);
382             let _ignored2 = delete_device(&dm, &device_diff);
383         }
384 
385         let target = DmCryptTargetBuilder::default()
386             .data_device(&data_device, sz)
387             .cipher(keyset.cipher)
388             .key(keyset.key)
389             .build()
390             .unwrap();
391         let target2 = DmCryptTargetBuilder::default()
392             .data_device(&data_device, sz)
393             .cipher(keyset.cipher)
394             .key(keyset.different_key)
395             .build()
396             .unwrap();
397 
398         let mut crypt_device = dm.create_crypt_device(device, &target).unwrap();
399 
400         write_to_dev(&crypt_device, inputimg);
401 
402         // Recreate the crypt device again diff key & check if the content is changed
403         crypt_device = dm.create_crypt_device(&device_diff, &target2).unwrap();
404         let crypt = read(crypt_device).unwrap();
405         assert_ne!(inputimg, crypt.as_slice());
406     }
407 }
408