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};
22 use clap::arg;
23 use dm::{crypt::CipherType, util};
24 use log::{error, info};
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;
29 use std::os::unix::fs::{FileTypeExt, PermissionsExt};
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";
35
main()36 fn main() {
37 android_logger::init_once(
38 android_logger::Config::default()
39 .with_tag("encryptedstore")
40 .with_max_level(log::LevelFilter::Info),
41 );
42
43 if let Err(e) = try_main() {
44 error!("{:?}", e);
45 std::process::exit(1)
46 }
47 }
48
try_main() -> Result<()>49 fn try_main() -> Result<()> {
50 info!("Starting encryptedstore binary");
51
52 let matches = clap_command().get_matches();
53
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());
57 // Note this error context is used in MicrodroidTests.
58 encryptedstore_init(blkdevice, key, mountpoint).with_context(|| {
59 format!(
60 "Unable to initialize encryptedstore on {:?} & mount at {:?}",
61 blkdevice, mountpoint
62 )
63 })?;
64 Ok(())
65 }
66
clap_command() -> clap::Command67 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
encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()>75 fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
76 ensure!(
77 std::fs::metadata(blkdevice)
78 .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))?
79 .file_type()
80 .is_block_device(),
81 "The path:{:?} is not of a block device",
82 blkdevice
83 );
84
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 }
95 mount(&crypt_device, mountpoint)
96 .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
97 if cfg!(multi_tenant) && needs_formatting {
98 set_root_dir_permissions(mountpoint)?;
99 }
100 Ok(())
101 }
102
set_root_dir_permissions(mountpoint: &Path) -> Result<()>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
enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf>112 fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
113 let dev_size = util::blkgetsize64(data_device)?;
114 let key = hex::decode(key).context("Unable to decode hex key")?;
115
116 // Create the dm-crypt spec
117 let target = dm::crypt::DmCryptTargetBuilder::default()
118 .data_device(data_device, dev_size)
119 .cipher(CipherType::AES256HCTR2)
120 .key(&key)
121 .opt_param("sector_size:4096")
122 .opt_param("iv_large_sectors")
123 .build()
124 .context("Couldn't build the DMCrypt target")?;
125 let dm = dm::DeviceMapper::new()?;
126 dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
127 }
128
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.
needs_formatting(data_device: &Path) -> Result<bool>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
format_ext4(device: &Path) -> Result<()>149 fn format_ext4(device: &Path) -> Result<()> {
150 let root_dir_uid_gid = format!(
151 "root_owner={}:{}",
152 microdroid_uids::ROOT_UID,
153 microdroid_uids::MICRODROID_PAYLOAD_GID
154 );
155 let mkfs_options = [
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",
162 "-b 4096", // block size in the filesystem,
163 "-E",
164 &root_dir_uid_gid,
165 ];
166 let mut cmd = Command::new(MK2FS_BIN);
167 let status = cmd
168 .args(mkfs_options)
169 .arg(device)
170 .status()
171 .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
172 ensure!(status.success(), "mkfs failed with {:?}", status);
173 Ok(())
174 }
175
mount(source: &Path, mountpoint: &Path) -> Result<()>176 fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
177 create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
178 let mount_options = CString::new(
179 "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
180 )
181 .unwrap();
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
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.
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 }
204
205 #[cfg(test)]
206 mod tests {
207 use super::*;
208
209 #[test]
verify_command()210 fn verify_command() {
211 // Check that the command parsing has been configured in a valid way.
212 clap_command().debug_assert();
213 }
214 }
215