Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2022 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 | //! `encryptedstore` is a program that (as the name indicates) provides encrypted storage |
| 18 | //! solution in a VM. This is based on dm-crypt & requires the (64 bytes') key & the backing device. |
| 19 | //! It uses dm_rust lib. |
| 20 | |
| 21 | use anyhow::{ensure, Context, Result}; |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 22 | use clap::arg; |
| 23 | use dm::{crypt::CipherType, util}; |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 24 | use log::{error, info}; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 25 | use std::ffi::CString; |
| 26 | use std::fs::{create_dir_all, OpenOptions}; |
| 27 | use std::io::{Error, Read, Write}; |
| 28 | use std::os::unix::ffi::OsStrExt; |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 29 | use std::os::unix::fs::{FileTypeExt, PermissionsExt}; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 30 | use std::path::{Path, PathBuf}; |
| 31 | use std::process::Command; |
| 32 | |
| 33 | const MK2FS_BIN: &str = "/system/bin/mke2fs"; |
| 34 | const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE"; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 35 | |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 36 | fn main() { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 37 | android_logger::init_once( |
| 38 | android_logger::Config::default() |
| 39 | .with_tag("encryptedstore") |
| 40 | .with_min_level(log::Level::Info), |
| 41 | ); |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 42 | |
| 43 | if let Err(e) = try_main() { |
| 44 | error!("{:?}", e); |
| 45 | std::process::exit(1) |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | fn try_main() -> Result<()> { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 50 | info!("Starting encryptedstore binary"); |
| 51 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 52 | let matches = clap_command().get_matches(); |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 53 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 54 | let blkdevice = Path::new(matches.get_one::<String>("blkdevice").unwrap()); |
| 55 | let key = matches.get_one::<String>("key").unwrap(); |
| 56 | let mountpoint = Path::new(matches.get_one::<String>("mountpoint").unwrap()); |
Shikha Panwar | 405aa69 | 2023-02-07 20:52:47 +0000 | [diff] [blame] | 57 | // Note this error context is used in MicrodroidTests. |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 58 | encryptedstore_init(blkdevice, key, mountpoint).with_context(|| { |
| 59 | format!( |
| 60 | "Unable to initialize encryptedstore on {:?} & mount at {:?}", |
| 61 | blkdevice, mountpoint |
| 62 | ) |
| 63 | })?; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 64 | Ok(()) |
| 65 | } |
| 66 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 67 | fn clap_command() -> clap::Command { |
| 68 | clap::Command::new("encryptedstore").args(&[ |
| 69 | arg!(--blkdevice <FILE> "the block device backing the encrypted storage").required(true), |
| 70 | arg!(--key <KEY> "key (in hex) equivalent to 32 bytes)").required(true), |
| 71 | arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true), |
| 72 | ]) |
| 73 | } |
| 74 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 75 | fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 76 | ensure!( |
Andrew Walbran | 48294fb | 2023-01-16 12:01:53 +0000 | [diff] [blame] | 77 | std::fs::metadata(blkdevice) |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 78 | .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))? |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 79 | .file_type() |
| 80 | .is_block_device(), |
| 81 | "The path:{:?} is not of a block device", |
| 82 | blkdevice |
| 83 | ); |
| 84 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 85 | let needs_formatting = |
| 86 | needs_formatting(blkdevice).context("Unable to check if formatting is required")?; |
| 87 | let crypt_device = |
| 88 | enable_crypt(blkdevice, key, "cryptdev").context("Unable to map crypt device")?; |
| 89 | |
| 90 | // We might need to format it with filesystem if this is a "seen-for-the-first-time" device. |
| 91 | if needs_formatting { |
| 92 | info!("Freshly formatting the crypt device"); |
| 93 | format_ext4(&crypt_device)?; |
| 94 | } |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 95 | mount(&crypt_device, mountpoint) |
| 96 | .with_context(|| format!("Unable to mount {:?}", crypt_device))?; |
Alan Stokes | 78a299f | 2023-09-01 11:16:18 +0100 | [diff] [blame] | 97 | if cfg!(payload_not_root) && needs_formatting { |
| 98 | set_root_dir_permissions(mountpoint)?; |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 99 | } |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 100 | Ok(()) |
| 101 | } |
| 102 | |
Alan Stokes | 78a299f | 2023-09-01 11:16:18 +0100 | [diff] [blame] | 103 | fn set_root_dir_permissions(mountpoint: &Path) -> Result<()> { |
| 104 | // mke2fs hardwires the root dir permissions as 0o755 which doesn't match what we want. |
| 105 | // We want to allow full access by both root and the payload group, and no access by anything |
| 106 | // else. And we want the sticky bit set, so different payload UIDs can create sub-directories |
| 107 | // that other payloads can't delete. |
| 108 | let permissions = PermissionsExt::from_mode(0o770 | libc::S_ISVTX); |
| 109 | std::fs::set_permissions(mountpoint, permissions).context("Failed to chmod root directory") |
| 110 | } |
| 111 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 112 | fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 113 | let dev_size = util::blkgetsize64(data_device)?; |
| 114 | let key = hex::decode(key).context("Unable to decode hex key")?; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 115 | |
| 116 | // Create the dm-crypt spec |
| 117 | let target = dm::crypt::DmCryptTargetBuilder::default() |
| 118 | .data_device(data_device, dev_size) |
Shikha Panwar | 195f89c | 2022-11-23 16:20:34 +0000 | [diff] [blame] | 119 | .cipher(CipherType::AES256HCTR2) |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 120 | .key(&key) |
Shikha Panwar | 6337d5b | 2023-02-09 13:02:33 +0000 | [diff] [blame] | 121 | .opt_param("sector_size:4096") |
| 122 | .opt_param("iv_large_sectors") |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 123 | .build() |
| 124 | .context("Couldn't build the DMCrypt target")?; |
| 125 | let dm = dm::DeviceMapper::new()?; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 126 | dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device") |
| 127 | } |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 128 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 129 | // The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device. |
| 130 | // This function looks for it, zeroing it, if present. |
| 131 | fn needs_formatting(data_device: &Path) -> Result<bool> { |
| 132 | let mut file = OpenOptions::new() |
| 133 | .read(true) |
| 134 | .write(true) |
| 135 | .open(data_device) |
| 136 | .with_context(|| format!("Failed to open {:?}", data_device))?; |
| 137 | |
| 138 | let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()]; |
| 139 | file.read_exact(&mut buf)?; |
| 140 | |
| 141 | if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() { |
| 142 | buf.fill(0); |
| 143 | file.write_all(&buf)?; |
| 144 | return Ok(true); |
| 145 | } |
| 146 | Ok(false) |
| 147 | } |
| 148 | |
| 149 | fn format_ext4(device: &Path) -> Result<()> { |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 150 | let root_dir_uid_gid = format!( |
| 151 | "root_owner={}:{}", |
| 152 | microdroid_uids::ROOT_UID, |
| 153 | microdroid_uids::MICRODROID_PAYLOAD_GID |
| 154 | ); |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 155 | let mkfs_options = [ |
Shikha Panwar | ab8591a | 2023-03-27 18:46:48 +0000 | [diff] [blame] | 156 | "-j", // Create appropriate sized journal |
| 157 | /* metadata_csum: enabled for filesystem integrity |
| 158 | * extents: Not enabling extents reduces the coverage of metadata checksumming. |
| 159 | * 64bit: larger fields afforded by this feature enable full-strength checksumming. |
| 160 | */ |
| 161 | "-O metadata_csum, extents, 64bit", |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 162 | "-b 4096", // block size in the filesystem, |
| 163 | "-E", |
| 164 | &root_dir_uid_gid, |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 165 | ]; |
| 166 | let mut cmd = Command::new(MK2FS_BIN); |
| 167 | let status = cmd |
| 168 | .args(mkfs_options) |
| 169 | .arg(device) |
| 170 | .status() |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 171 | .with_context(|| format!("failed to execute {}", MK2FS_BIN))?; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 172 | ensure!(status.success(), "mkfs failed with {:?}", status); |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 173 | Ok(()) |
| 174 | } |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 175 | |
| 176 | fn mount(source: &Path, mountpoint: &Path) -> Result<()> { |
Alan Stokes | 1294f94 | 2023-08-21 14:34:12 +0100 | [diff] [blame] | 177 | create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?; |
Shikha Panwar | d4ce1c0 | 2022-12-08 18:05:21 +0000 | [diff] [blame] | 178 | let mount_options = CString::new( |
| 179 | "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0", |
| 180 | ) |
| 181 | .unwrap(); |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 182 | let source = CString::new(source.as_os_str().as_bytes())?; |
| 183 | let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?; |
| 184 | let fstype = CString::new("ext4").unwrap(); |
| 185 | |
Andrew Walbran | ae3350d | 2023-07-21 19:01:18 +0100 | [diff] [blame] | 186 | // SAFETY: The source, target and filesystemtype are valid C strings. For ext4, data is expected |
| 187 | // to be a C string as well, which it is. None of these pointers are retained after mount |
| 188 | // returns. |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 189 | let ret = unsafe { |
| 190 | libc::mount( |
| 191 | source.as_ptr(), |
| 192 | mountpoint.as_ptr(), |
| 193 | fstype.as_ptr(), |
| 194 | libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC, |
| 195 | mount_options.as_ptr() as *const std::ffi::c_void, |
| 196 | ) |
| 197 | }; |
| 198 | if ret < 0 { |
| 199 | Err(Error::last_os_error()).context("mount failed") |
| 200 | } else { |
| 201 | Ok(()) |
| 202 | } |
| 203 | } |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 204 | |
| 205 | #[cfg(test)] |
| 206 | mod tests { |
| 207 | use super::*; |
| 208 | |
| 209 | #[test] |
| 210 | fn verify_command() { |
| 211 | // Check that the command parsing has been configured in a valid way. |
| 212 | clap_command().debug_assert(); |
| 213 | } |
| 214 | } |