Mount a filesystem on the crypt block device

Encryptedstore binary will format an ext4 fs (if required) in the crypt
device & mount it.

This patch also adds "ENCRYPTEDSTORE" as Partition type & formatting the
raw device with appropriate magic to identify this device is not yet
formatted with a filesystem.

Bug: 241541860
Test: Run a vm -> create a file
Test: Re-run the VM (with the same vm-instance) & check that it
persists.

Change-Id: I0ec92042f3f5f35f2735108cc68b68c022493c85
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
index 301e733..13ef1b9 100644
--- a/encryptedstore/Android.bp
+++ b/encryptedstore/Android.bp
@@ -10,6 +10,7 @@
     rustlibs: [
         "libandroid_logger",
         "libanyhow",
+        "liblibc",
         "libclap",
         "libhex",
         "liblog_rust",
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 9a701ed..d7d2382 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -22,8 +22,16 @@
 use clap::{arg, App};
 use dm::{crypt::CipherType, util};
 use log::info;
+use std::ffi::CString;
+use std::fs::{create_dir_all, OpenOptions};
+use std::io::{Error, Read, Write};
+use std::os::unix::ffi::OsStrExt;
 use std::os::unix::fs::FileTypeExt;
-use std::path::Path;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+const MK2FS_BIN: &str = "/system/bin/mke2fs";
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
 fn main() -> Result<()> {
     android_logger::init_once(
@@ -38,11 +46,21 @@
             arg!(--blkdevice <FILE> "the block device backing the encrypted storage")
                 .required(true),
             arg!(--key <KEY> "key (in hex) equivalent to 64 bytes)").required(true),
+            arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true),
         ])
         .get_matches();
 
     let blkdevice = Path::new(matches.value_of("blkdevice").unwrap());
     let key = matches.value_of("key").unwrap();
+    let mountpoint = Path::new(matches.value_of("mountpoint").unwrap());
+    encryptedstore_init(blkdevice, key, mountpoint).context(format!(
+        "Unable to initialize encryptedstore on {:?} & mount at {:?}",
+        blkdevice, mountpoint
+    ))?;
+    Ok(())
+}
+
+fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
     ensure!(
         std::fs::metadata(&blkdevice)
             .context(format!("Failed to get metadata of {:?}", blkdevice))?
@@ -52,11 +70,21 @@
         blkdevice
     );
 
-    enable_crypt(blkdevice, key, "cryptdev")?;
+    let needs_formatting =
+        needs_formatting(blkdevice).context("Unable to check if formatting is required")?;
+    let crypt_device =
+        enable_crypt(blkdevice, key, "cryptdev").context("Unable to map crypt device")?;
+
+    // We might need to format it with filesystem if this is a "seen-for-the-first-time" device.
+    if needs_formatting {
+        info!("Freshly formatting the crypt device");
+        format_ext4(&crypt_device)?;
+    }
+    mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?;
     Ok(())
 }
 
-fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<()> {
+fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
     let dev_size = util::blkgetsize64(data_device)?;
     let key = hex::decode(key).context("Unable to decode hex key")?;
     ensure!(key.len() == 64, "We need 64 bytes' key for aes-xts cipher for block encryption");
@@ -69,7 +97,63 @@
         .build()
         .context("Couldn't build the DMCrypt target")?;
     let dm = dm::DeviceMapper::new()?;
-    dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")?;
+    dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
+}
 
+// The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device.
+// This function looks for it, zeroing it, if present.
+fn needs_formatting(data_device: &Path) -> Result<bool> {
+    let mut file = OpenOptions::new()
+        .read(true)
+        .write(true)
+        .open(data_device)
+        .with_context(|| format!("Failed to open {:?}", data_device))?;
+
+    let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()];
+    file.read_exact(&mut buf)?;
+
+    if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() {
+        buf.fill(0);
+        file.write_all(&buf)?;
+        return Ok(true);
+    }
+    Ok(false)
+}
+
+fn format_ext4(device: &Path) -> Result<()> {
+    let mkfs_options = [
+        "-j",               // Create appropriate sized journal
+        "-O metadata_csum", // Metadata checksum for filesystem integrity
+    ];
+    let mut cmd = Command::new(MK2FS_BIN);
+    let status = cmd
+        .args(mkfs_options)
+        .arg(device)
+        .status()
+        .context(format!("failed to execute {}", MK2FS_BIN))?;
+    ensure!(status.success(), "mkfs failed with {:?}", status);
     Ok(())
 }
+
+fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
+    create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
+    let mount_options = CString::new("").unwrap();
+    let source = CString::new(source.as_os_str().as_bytes())?;
+    let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
+    let fstype = CString::new("ext4").unwrap();
+
+    let ret = unsafe {
+        libc::mount(
+            source.as_ptr(),
+            mountpoint.as_ptr(),
+            fstype.as_ptr(),
+            libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC,
+            mount_options.as_ptr() as *const std::ffi::c_void,
+        )
+    };
+    if ret < 0 {
+        Err(Error::last_os_error()).context("mount failed")
+    } else {
+        Ok(())
+    }
+}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index d73a03c..f2b223d 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -84,6 +84,7 @@
         "microdroid_plat_sepolicy_and_mapping.sha256",
         "microdroid_property_contexts",
         "microdroid_service_contexts",
+        "mke2fs",
 
         // TODO(b/195425111) these should be added automatically
         "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 4ebcc2e..a53b401 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -88,6 +88,7 @@
 const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
 const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
 const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+const ENCRYPTEDSTORE_MOUNTPOINT: &str = "/mnt/encryptedstore";
 
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
@@ -820,6 +821,7 @@
         .arg(ENCRYPTEDSTORE_BACKING_DEVICE)
         .arg("--key")
         .arg(hex::encode(&*key))
+        .args(["--mountpoint", ENCRYPTEDSTORE_MOUNTPOINT])
         .spawn()
         .context("encryptedstore failed")
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
index f25e674..774681a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
@@ -29,4 +29,8 @@
      * The partition is initialized as an instance image which is formatted to hold per-VM secrets
      */
     ANDROID_VM_INSTANCE = 1,
+    /**
+     * The partition is initialized to back encryptedstore disk image formatted to indicate intent
+     */
+    ENCRYPTEDSTORE = 2,
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index cfcfa2b..578960c 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -98,6 +98,8 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+
 /// Singleton service for allocating globally-unique VM resources, such as the CID, and running
 /// singleton servers, like tombstone receiver.
 #[derive(Debug, Default)]
@@ -261,6 +263,7 @@
         match partition_type {
             PartitionType::RAW => Ok(()),
             PartitionType::ANDROID_VM_INSTANCE => format_as_android_vm_instance(&mut part),
+            PartitionType::ENCRYPTEDSTORE => format_as_encryptedstore(&mut part),
             _ => Err(Error::new(
                 ErrorKind::Unsupported,
                 format!("Unsupported partition type {:?}", partition_type),
@@ -593,6 +596,11 @@
     part.flush()
 }
 
+fn format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()> {
+    part.write_all(UNFORMATTED_STORAGE_MAGIC.as_bytes())?;
+    part.flush()
+}
+
 fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
     File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
 }
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 1f0433d..01b916b 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -103,7 +103,7 @@
                 service,
                 path,
                 storage_size.unwrap_or(10 * 1024 * 1024),
-                PartitionType::RAW,
+                PartitionType::ENCRYPTEDSTORE,
             )?;
         }
         Some(open_parcel_file(path, true)?)