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