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