blob: 8647003fcffa358ae81d666aabf4ada3a19aac46 [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
Shraddha Basantwania9a9c4f2025-02-25 09:51:48 -080033const E2FSCK_BIN: &str = "/system/bin/e2fsck";
Shikha Panwar9fd198f2022-11-18 17:43:43 +000034const MK2FS_BIN: &str = "/system/bin/mke2fs";
Shraddha Basantwania9a9c4f2025-02-25 09:51:48 -080035const RESIZE2FS_BIN: &str = "/system/bin/resize2fs";
Shikha Panwar9fd198f2022-11-18 17:43:43 +000036const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
Shikha Panwar566c9672022-11-15 14:39:58 +000037
Alan Stokes1294f942023-08-21 14:34:12 +010038fn main() {
Shikha Panwar566c9672022-11-15 14:39:58 +000039 android_logger::init_once(
40 android_logger::Config::default()
41 .with_tag("encryptedstore")
Jeff Vander Stoep57da1572024-01-31 10:52:16 +010042 .with_max_level(log::LevelFilter::Info),
Shikha Panwar566c9672022-11-15 14:39:58 +000043 );
Alan Stokes1294f942023-08-21 14:34:12 +010044
45 if let Err(e) = try_main() {
46 error!("{:?}", e);
47 std::process::exit(1)
48 }
49}
50
51fn try_main() -> Result<()> {
Shikha Panwar566c9672022-11-15 14:39:58 +000052 info!("Starting encryptedstore binary");
53
Andrew Walbranaa1efc42022-08-10 13:33:57 +000054 let matches = clap_command().get_matches();
Shikha Panwar566c9672022-11-15 14:39:58 +000055
Andrew Walbranaa1efc42022-08-10 13:33:57 +000056 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 Panwar405aa692023-02-07 20:52:47 +000059 // Note this error context is used in MicrodroidTests.
Alan Stokes1294f942023-08-21 14:34:12 +010060 encryptedstore_init(blkdevice, key, mountpoint).with_context(|| {
61 format!(
62 "Unable to initialize encryptedstore on {:?} & mount at {:?}",
63 blkdevice, mountpoint
64 )
65 })?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +000066 Ok(())
67}
68
Andrew Walbranaa1efc42022-08-10 13:33:57 +000069fn 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 Panwar9fd198f2022-11-18 17:43:43 +000077fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
Shikha Panwar566c9672022-11-15 14:39:58 +000078 ensure!(
Andrew Walbran48294fb2023-01-16 12:01:53 +000079 std::fs::metadata(blkdevice)
Alan Stokes1294f942023-08-21 14:34:12 +010080 .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))?
Shikha Panwar566c9672022-11-15 14:39:58 +000081 .file_type()
82 .is_block_device(),
83 "The path:{:?} is not of a block device",
84 blkdevice
85 );
86
Shikha Panwar9fd198f2022-11-18 17:43:43 +000087 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 Basantwania9a9c4f2025-02-25 09:51:48 -080096 } else {
97 resize_fs(&crypt_device)?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +000098 }
Alan Stokes1294f942023-08-21 14:34:12 +010099 mount(&crypt_device, mountpoint)
100 .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
Alan Stokes27f3ef02023-09-29 15:09:35 +0100101 if cfg!(multi_tenant) && needs_formatting {
Alan Stokes78a299f2023-09-01 11:16:18 +0100102 set_root_dir_permissions(mountpoint)?;
Alan Stokes1294f942023-08-21 14:34:12 +0100103 }
Shikha Panwar566c9672022-11-15 14:39:58 +0000104 Ok(())
105}
106
Alan Stokes78a299f2023-09-01 11:16:18 +0100107fn 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 Panwar9fd198f2022-11-18 17:43:43 +0000116fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
Shikha Panwar566c9672022-11-15 14:39:58 +0000117 let dev_size = util::blkgetsize64(data_device)?;
118 let key = hex::decode(key).context("Unable to decode hex key")?;
Shikha Panwar566c9672022-11-15 14:39:58 +0000119
120 // Create the dm-crypt spec
121 let target = dm::crypt::DmCryptTargetBuilder::default()
122 .data_device(data_device, dev_size)
Shikha Panwar195f89c2022-11-23 16:20:34 +0000123 .cipher(CipherType::AES256HCTR2)
Shikha Panwar566c9672022-11-15 14:39:58 +0000124 .key(&key)
Shikha Panwar6337d5b2023-02-09 13:02:33 +0000125 .opt_param("sector_size:4096")
126 .opt_param("iv_large_sectors")
Shikha Panwar0dc78b02025-02-06 16:23:45 +0000127 .opt_param("allow_discards") // This allows re-compaction of underlying disk img in host
Shikha Panwar566c9672022-11-15 14:39:58 +0000128 .build()
129 .context("Couldn't build the DMCrypt target")?;
130 let dm = dm::DeviceMapper::new()?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000131 dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
132}
Shikha Panwar566c9672022-11-15 14:39:58 +0000133
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000134// 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.
136fn 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
154fn format_ext4(device: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100155 let root_dir_uid_gid = format!(
156 "root_owner={}:{}",
157 microdroid_uids::ROOT_UID,
158 microdroid_uids::MICRODROID_PAYLOAD_GID
159 );
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000160 let mkfs_options = [
Shikha Panwarab8591a2023-03-27 18:46:48 +0000161 "-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 Stokes1294f942023-08-21 14:34:12 +0100167 "-b 4096", // block size in the filesystem,
168 "-E",
169 &root_dir_uid_gid,
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000170 ];
171 let mut cmd = Command::new(MK2FS_BIN);
172 let status = cmd
173 .args(mkfs_options)
174 .arg(device)
175 .status()
Alan Stokes1294f942023-08-21 14:34:12 +0100176 .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000177 ensure!(status.success(), "mkfs failed with {:?}", status);
Shikha Panwar566c9672022-11-15 14:39:58 +0000178 Ok(())
179}
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000180
Shraddha Basantwania9a9c4f2025-02-25 09:51:48 -0800181fn 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 Panwar9fd198f2022-11-18 17:43:43 +0000202fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100203 create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
Shikha Panward4ce1c02022-12-08 18:05:21 +0000204 let mount_options = CString::new(
Shikha Panwar0dc78b02025-02-06 16:23:45 +0000205 "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0,discard",
Shikha Panward4ce1c02022-12-08 18:05:21 +0000206 )
207 .unwrap();
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000208 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 Walbranae3350d2023-07-21 19:01:18 +0100212 // 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 Panwar9fd198f2022-11-18 17:43:43 +0000215 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 Walbranaa1efc42022-08-10 13:33:57 +0000230
231#[cfg(test)]
232mod 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}