blob: 983e3e906f6200b7ada6f5f6295e15d142151420 [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 Panwar566c9672022-11-15 14:39:58 +0000123 .build()
124 .context("Couldn't build the DMCrypt target")?;
125 let dm = dm::DeviceMapper::new()?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000126 dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
127}
Shikha Panwar566c9672022-11-15 14:39:58 +0000128
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000129// 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.
131fn 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
149fn format_ext4(device: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100150 let root_dir_uid_gid = format!(
151 "root_owner={}:{}",
152 microdroid_uids::ROOT_UID,
153 microdroid_uids::MICRODROID_PAYLOAD_GID
154 );
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000155 let mkfs_options = [
Shikha Panwarab8591a2023-03-27 18:46:48 +0000156 "-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 Stokes1294f942023-08-21 14:34:12 +0100162 "-b 4096", // block size in the filesystem,
163 "-E",
164 &root_dir_uid_gid,
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000165 ];
166 let mut cmd = Command::new(MK2FS_BIN);
167 let status = cmd
168 .args(mkfs_options)
169 .arg(device)
170 .status()
Alan Stokes1294f942023-08-21 14:34:12 +0100171 .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000172 ensure!(status.success(), "mkfs failed with {:?}", status);
Shikha Panwar566c9672022-11-15 14:39:58 +0000173 Ok(())
174}
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000175
176fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
Alan Stokes1294f942023-08-21 14:34:12 +0100177 create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
Shikha Panward4ce1c02022-12-08 18:05:21 +0000178 let mount_options = CString::new(
179 "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
180 )
181 .unwrap();
Shikha Panwar9fd198f2022-11-18 17:43:43 +0000182 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 Walbranae3350d2023-07-21 19:01:18 +0100186 // 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 Panwar9fd198f2022-11-18 17:43:43 +0000189 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 Walbranaa1efc42022-08-10 13:33:57 +0000204
205#[cfg(test)]
206mod 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}