blob: dd4ee3b20a6be852eada7fb26df0416a7e19e98b [file] [log] [blame]
Shikha Panwar566c9672022-11-15 14:39:58 +00001/*
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
21use anyhow::{ensure, Context, Result};
Andrew Walbranaa1efc42022-08-10 13:33:57 +000022use clap::arg;
23use dm::{crypt::CipherType, util};
Alan Stokes1294f942023-08-21 14:34:12 +010024use log::{error, info};
Shikha Panwar9fd198f2022-11-18 17:43:43 +000025use std::ffi::CString;
26use std::fs::{create_dir_all, OpenOptions};
27use std::io::{Error, Read, Write};
28use std::os::unix::ffi::OsStrExt;
Alan Stokes1294f942023-08-21 14:34:12 +010029use std::os::unix::fs::{FileTypeExt, PermissionsExt};
Shikha Panwar9fd198f2022-11-18 17:43:43 +000030use std::path::{Path, PathBuf};
31use std::process::Command;
32
33const MK2FS_BIN: &str = "/system/bin/mke2fs";
34const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
Shikha Panwar566c9672022-11-15 14:39:58 +000035
Alan Stokes1294f942023-08-21 14:34:12 +010036fn main() {
Shikha Panwar566c9672022-11-15 14:39:58 +000037 android_logger::init_once(
38 android_logger::Config::default()
39 .with_tag("encryptedstore")
Jeff Vander Stoep57da1572024-01-31 10:52:16 +010040 .with_max_level(log::LevelFilter::Info),
Shikha Panwar566c9672022-11-15 14:39:58 +000041 );
Alan Stokes1294f942023-08-21 14:34:12 +010042
43 if let Err(e) = try_main() {
44 error!("{:?}", e);
45 std::process::exit(1)
46 }
47}
48
49fn try_main() -> Result<()> {
Shikha Panwar566c9672022-11-15 14:39:58 +000050 info!("Starting encryptedstore binary");
51
Andrew Walbranaa1efc42022-08-10 13:33:57 +000052 let matches = clap_command().get_matches();
Shikha Panwar566c9672022-11-15 14:39:58 +000053
Andrew Walbranaa1efc42022-08-10 13:33:57 +000054 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 Panwar405aa692023-02-07 20:52:47 +000057 // Note this error context is used in MicrodroidTests.
Alan Stokes1294f942023-08-21 14:34:12 +010058 encryptedstore_init(blkdevice, key, mountpoint).with_context(|| {
59 format!(
60 "Unable to initialize encryptedstore on {:?} & mount at {:?}",
61 blkdevice, mountpoint
62 )
63 })?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +000064 Ok(())
65}
66
Andrew Walbranaa1efc42022-08-10 13:33:57 +000067fn 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 Panwar9fd198f2022-11-18 17:43:43 +000075fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
Shikha Panwar566c9672022-11-15 14:39:58 +000076 ensure!(
Andrew Walbran48294fb2023-01-16 12:01:53 +000077 std::fs::metadata(blkdevice)
Alan Stokes1294f942023-08-21 14:34:12 +010078 .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))?
Shikha Panwar566c9672022-11-15 14:39:58 +000079 .file_type()
80 .is_block_device(),
81 "The path:{:?} is not of a block device",
82 blkdevice
83 );
84
Shikha Panwar9fd198f2022-11-18 17:43:43 +000085 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 Stokes1294f942023-08-21 14:34:12 +010095 mount(&crypt_device, mountpoint)
96 .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
Alan Stokes27f3ef02023-09-29 15:09:35 +010097 if cfg!(multi_tenant) && needs_formatting {
Alan Stokes78a299f2023-09-01 11:16:18 +010098 set_root_dir_permissions(mountpoint)?;
Alan Stokes1294f942023-08-21 14:34:12 +010099 }
Shikha Panwar566c9672022-11-15 14:39:58 +0000100 Ok(())
101}
102
Alan Stokes78a299f2023-09-01 11:16:18 +0100103fn 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 Panwar9fd198f2022-11-18 17:43:43 +0000112fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
Shikha Panwar566c9672022-11-15 14:39:58 +0000113 let dev_size = util::blkgetsize64(data_device)?;
114 let key = hex::decode(key).context("Unable to decode hex key")?;
Shikha Panwar566c9672022-11-15 14:39:58 +0000115
116 // Create the dm-crypt spec
117 let target = dm::crypt::DmCryptTargetBuilder::default()
118 .data_device(data_device, dev_size)
Shikha Panwar195f89c2022-11-23 16:20:34 +0000119 .cipher(CipherType::AES256HCTR2)
Shikha Panwar566c9672022-11-15 14:39:58 +0000120 .key(&key)
Shikha Panwar6337d5b2023-02-09 13:02:33 +0000121 .opt_param("sector_size:4096")
122 .opt_param("iv_large_sectors")
Shikha Panwar0dc78b02025-02-06 16:23:45 +0000123 .opt_param("allow_discards") // This allows re-compaction of underlying disk img in host
Shikha Panwar566c9672022-11-15 14:39:58 +0000124 .build()
125 .context("Couldn't build the DMCrypt target")?;
126 let dm = dm::DeviceMapper::new()?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000127 dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
128}
Shikha Panwar566c9672022-11-15 14:39:58 +0000129
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000130// The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device.
131// This function looks for it, zeroing it, if present.
132fn needs_formatting(data_device: &Path) -> Result<bool> {
133 let mut file = OpenOptions::new()
134 .read(true)
135 .write(true)
136 .open(data_device)
137 .with_context(|| format!("Failed to open {:?}", data_device))?;
138
139 let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()];
140 file.read_exact(&mut buf)?;
141
142 if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() {
143 buf.fill(0);
144 file.write_all(&buf)?;
145 return Ok(true);
146 }
147 Ok(false)
148}
149
150fn format_ext4(device: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100151 let root_dir_uid_gid = format!(
152 "root_owner={}:{}",
153 microdroid_uids::ROOT_UID,
154 microdroid_uids::MICRODROID_PAYLOAD_GID
155 );
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000156 let mkfs_options = [
Shikha Panwarab8591a2023-03-27 18:46:48 +0000157 "-j", // Create appropriate sized journal
158 /* metadata_csum: enabled for filesystem integrity
159 * extents: Not enabling extents reduces the coverage of metadata checksumming.
160 * 64bit: larger fields afforded by this feature enable full-strength checksumming.
161 */
162 "-O metadata_csum, extents, 64bit",
Alan Stokes1294f942023-08-21 14:34:12 +0100163 "-b 4096", // block size in the filesystem,
164 "-E",
165 &root_dir_uid_gid,
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000166 ];
167 let mut cmd = Command::new(MK2FS_BIN);
168 let status = cmd
169 .args(mkfs_options)
170 .arg(device)
171 .status()
Alan Stokes1294f942023-08-21 14:34:12 +0100172 .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000173 ensure!(status.success(), "mkfs failed with {:?}", status);
Shikha Panwar566c9672022-11-15 14:39:58 +0000174 Ok(())
175}
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000176
177fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100178 create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
Shikha Panward4ce1c02022-12-08 18:05:21 +0000179 let mount_options = CString::new(
Shikha Panwar0dc78b02025-02-06 16:23:45 +0000180 "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0,discard",
Shikha Panward4ce1c02022-12-08 18:05:21 +0000181 )
182 .unwrap();
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000183 let source = CString::new(source.as_os_str().as_bytes())?;
184 let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
185 let fstype = CString::new("ext4").unwrap();
186
Andrew Walbranae3350d2023-07-21 19:01:18 +0100187 // SAFETY: The source, target and filesystemtype are valid C strings. For ext4, data is expected
188 // to be a C string as well, which it is. None of these pointers are retained after mount
189 // returns.
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000190 let ret = unsafe {
191 libc::mount(
192 source.as_ptr(),
193 mountpoint.as_ptr(),
194 fstype.as_ptr(),
195 libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC,
196 mount_options.as_ptr() as *const std::ffi::c_void,
197 )
198 };
199 if ret < 0 {
200 Err(Error::last_os_error()).context("mount failed")
201 } else {
202 Ok(())
203 }
204}
Andrew Walbranaa1efc42022-08-10 13:33:57 +0000205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn verify_command() {
212 // Check that the command parsing has been configured in a valid way.
213 clap_command().debug_assert();
214 }
215}