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