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