Merge "authfs: expose fsverity digest via xattr"
diff --git a/compos/Android.bp b/compos/Android.bp
index e29387d..faf9576 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -39,6 +39,7 @@
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
         "libclap",
+        "liblibc",
         "liblog_rust",
         "libminijail_rust",
         "libring",
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 53302e8..24266e6 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
+use libc::getxattr;
 use log::error;
 use minijail::{self, Minijail};
-use std::os::unix::io::AsRawFd;
+use std::ffi::CString;
+use std::fs::File;
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::Path;
 
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
@@ -31,6 +35,22 @@
 /// meaningless in the current process.
 pub type PseudoRawFd = i32;
 
+const SHA256_HASH_SIZE: usize = 32;
+type Sha256Hash = [u8; SHA256_HASH_SIZE];
+
+pub enum CompilerOutput {
+    /// Fs-verity digests of output files, if the compiler finishes successfully.
+    Digests { oat: Sha256Hash, vdex: Sha256Hash, image: Sha256Hash },
+    /// Exit code returned by the compiler, if not 0.
+    ExitCode(i8),
+}
+
+struct CompilerOutputParcelFds {
+    oat: ParcelFileDescriptor,
+    vdex: ParcelFileDescriptor,
+    image: ParcelFileDescriptor,
+}
+
 /// Runs the compiler with given flags with file descriptors described in `metadata` retrieved via
 /// `authfs_service`. Returns exit code of the compiler process.
 pub fn compile(
@@ -38,8 +58,9 @@
     compiler_args: &[String],
     authfs_service: Strong<dyn IAuthFsService>,
     metadata: &Metadata,
-) -> Result<i8> {
-    // Mount authfs (via authfs_service).
+) -> Result<CompilerOutput> {
+    // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
+    // is out of scope.
     let authfs_config = build_authfs_config(metadata);
     let authfs = authfs_service.mount(&authfs_config)?;
 
@@ -54,14 +75,20 @@
         spawn_jailed_task(compiler_path, compiler_args, fd_mapping).context("Spawn dex2oat")?;
     let jail_result = jail.wait();
 
-    // Be explicit about the lifetime, which should last at least until the task is finished.
-    drop(authfs);
+    let parcel_fds = parse_compiler_args(&authfs, compiler_args)?;
+    let oat_file: &File = parcel_fds.oat.as_ref();
+    let vdex_file: &File = parcel_fds.vdex.as_ref();
+    let image_file: &File = parcel_fds.image.as_ref();
 
     match jail_result {
-        Ok(()) => Ok(0), // TODO(b/161471326): Sign the output on succeed.
+        Ok(()) => Ok(CompilerOutput::Digests {
+            oat: fsverity_measure(oat_file.as_raw_fd())?,
+            vdex: fsverity_measure(vdex_file.as_raw_fd())?,
+            image: fsverity_measure(image_file.as_raw_fd())?,
+        }),
         Err(minijail::Error::ReturnCode(exit_code)) => {
-            error!("Task failed with exit code {}", exit_code);
-            Ok(exit_code as i8)
+            error!("dex2oat failed with exit code {}", exit_code);
+            Ok(CompilerOutput::ExitCode(exit_code as i8))
         }
         Err(e) => {
             bail!("Unexpected minijail error: {}", e)
@@ -69,6 +96,46 @@
     }
 }
 
+fn parse_compiler_args(
+    authfs: &Strong<dyn IAuthFs>,
+    args: &[String],
+) -> Result<CompilerOutputParcelFds> {
+    const OAT_FD_PREFIX: &str = "--oat-fd=";
+    const VDEX_FD_PREFIX: &str = "--output-vdex-fd=";
+    const IMAGE_FD_PREFIX: &str = "--image-fd=";
+    const APP_IMAGE_FD_PREFIX: &str = "--app-image-fd=";
+
+    let mut oat = None;
+    let mut vdex = None;
+    let mut image = None;
+
+    for arg in args {
+        if let Some(value) = arg.strip_prefix(OAT_FD_PREFIX) {
+            let fd = value.parse::<RawFd>().context("Invalid --oat-fd flag")?;
+            debug_assert!(oat.is_none());
+            oat = Some(authfs.openFile(fd, false)?);
+        } else if let Some(value) = arg.strip_prefix(VDEX_FD_PREFIX) {
+            let fd = value.parse::<RawFd>().context("Invalid --output-vdex-fd flag")?;
+            debug_assert!(vdex.is_none());
+            vdex = Some(authfs.openFile(fd, false)?);
+        } else if let Some(value) = arg.strip_prefix(IMAGE_FD_PREFIX) {
+            let fd = value.parse::<RawFd>().context("Invalid --image-fd flag")?;
+            debug_assert!(image.is_none());
+            image = Some(authfs.openFile(fd, false)?);
+        } else if let Some(value) = arg.strip_prefix(APP_IMAGE_FD_PREFIX) {
+            let fd = value.parse::<RawFd>().context("Invalid --app-image-fd flag")?;
+            debug_assert!(image.is_none());
+            image = Some(authfs.openFile(fd, false)?);
+        }
+    }
+
+    Ok(CompilerOutputParcelFds {
+        oat: oat.ok_or_else(|| anyhow!("Missing --oat-fd"))?,
+        vdex: vdex.ok_or_else(|| anyhow!("Missing --vdex-fd"))?,
+        image: image.ok_or_else(|| anyhow!("Missing --image-fd or --app-image-fd"))?,
+    })
+}
+
 fn build_authfs_config(metadata: &Metadata) -> AuthFsConfig {
     AuthFsConfig {
         port: 3264, // TODO: support dynamic port
@@ -119,3 +186,22 @@
     let _pid = jail.run_remap(executable, preserve_fds.as_slice(), args)?;
     Ok(jail)
 }
+
+fn fsverity_measure(fd: RawFd) -> Result<Sha256Hash> {
+    // TODO(b/196635431): Unfortunately, the FUSE API doesn't allow authfs to implement the standard
+    // fs-verity ioctls. Until the kernel allows, use the alternative xattr that authfs provides.
+    let path = CString::new(format!("/proc/self/fd/{}", fd).as_str()).unwrap();
+    let name = CString::new("authfs.fsverity.digest").unwrap();
+    let mut buf = [0u8; SHA256_HASH_SIZE];
+    // SAFETY: getxattr should not write beyond the given buffer size.
+    let size = unsafe {
+        getxattr(path.as_ptr(), name.as_ptr(), buf.as_mut_ptr() as *mut libc::c_void, buf.len())
+    };
+    if size < 0 {
+        bail!("Failed to getxattr: {}", io::Error::last_os_error());
+    } else if size != SHA256_HASH_SIZE as isize {
+        bail!("Unexpected hash size: {}", size);
+    } else {
+        Ok(buf)
+    }
+}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index b5edd98..8fe4795 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,11 +19,11 @@
 //! actual compiler.
 
 use anyhow::Result;
-use log::warn;
+use log::{debug, warn};
 use std::ffi::CString;
 use std::path::PathBuf;
 
-use crate::compilation::compile;
+use crate::compilation::{compile, CompilerOutput};
 use crate::compos_key_service::CompOsKeyService;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
@@ -57,12 +57,22 @@
 impl ICompOsService for CompOsService {
     fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> {
         let authfs_service = get_authfs_service()?;
-        compile(&self.dex2oat_path, args, authfs_service, metadata).map_err(|e| {
+        let output = compile(&self.dex2oat_path, args, authfs_service, metadata).map_err(|e| {
             new_binder_exception(
                 ExceptionCode::SERVICE_SPECIFIC,
                 format!("Compilation failed: {}", e),
             )
-        })
+        })?;
+        match output {
+            CompilerOutput::Digests { oat, vdex, image } => {
+                // TODO(b/161471326): Sign the output on succeed.
+                debug!("oat fs-verity digest: {:02x?}", oat);
+                debug!("vdex fs-verity digest: {:02x?}", vdex);
+                debug!("image fs-verity digest: {:02x?}", image);
+                Ok(0)
+            }
+            CompilerOutput::ExitCode(exit_code) => Ok(exit_code),
+        }
     }
 
     fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
diff --git a/microdroid/README.md b/microdroid/README.md
index 0578921..196c543 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -7,7 +7,7 @@
 
 ## Prerequisites
 
-Any 64-bit target (either x86\_64 or arm64) is supported. 32-bit target is not
+Any 64-bit target (either x86_64 or arm64) is supported. 32-bit target is not
 supported. Note that we currently don't support user builds; only userdebug
 builds are supported.
 
@@ -39,7 +39,7 @@
 adb reboot
 ```
 
-If your target is x86\_64 (e.g. `aosp_cf_x86_64_phone`), replace `aosp_arm64`
+If your target is x86_64 (e.g. `aosp_cf_x86_64_phone`), replace `aosp_arm64`
 with `aosp_x86_64`.
 
 ## Building an app
@@ -69,7 +69,7 @@
 
 ```json
 {
-  "os": {"name": "microdroid"},
+  "os": { "name": "microdroid" },
   "task": {
     "type": "microdroid_launcher",
     "command": "MyMicrodroidApp.so"
@@ -78,7 +78,7 @@
 ```
 
 The value of `task.command` should match with the name of the shared library
-defined above. If your app rquires APEXes to be imported, you can declare the
+defined above. If your app requires APEXes to be imported, you can declare the
 list in `apexes` key like following.
 
 ```json
@@ -134,6 +134,7 @@
 
 `ALL_CAP`s below are placeholders. They need to be replaced with correct
 values:
+
 * `VM_CONFIG_FILE`: the name of the VM config file that you embedded in the APK.
   (e.g. `vm_config.json`)
 * `PACKAGE_NAME_OF_YOUR_APP`: package name of your app (e.g. `com.acme.app`).
@@ -174,10 +175,10 @@
 Stopping the VM can be done as follows:
 
 ```sh
-adb shell /apex/com.android.virt/bin/vm stop CID
+adb shell /apex/com.android.virt/bin/vm stop $CID
 ```
 
-, where `CID` is the reported CID value. This works only when the `vm` was
+, where `$CID` is the reported CID value. This works only when the `vm` was
 invoked with the `--daemonize` flag. If the flag was not used, press Ctrl+C on
 the console where the `vm run-app` command was invoked.
 
@@ -190,10 +191,10 @@
 adb connect localhost:8000
 ```
 
-`CID` should be the CID that `vm` reported upon execution of the `vm run`
-command in the above. You can also check it with `adb shell
-"/apex/com.android.virt/bin/vm list"`. `5555` must be
-the value. `8000` however can be any port in the development machine.
+`$CID` should be the CID that `vm` reported upon execution of the `vm run`
+command in the above. You can also check it with
+`adb shell "/apex/com.android.virt/bin/vm list"`. `5555` must be the value.
+`8000` however can be any port on the development machine.
 
 Done. Now you can log into microdroid. Have fun!
 
diff --git a/microdroid/payload/Android.bp b/microdroid/payload/Android.bp
index 72711c3..f77c037 100644
--- a/microdroid/payload/Android.bp
+++ b/microdroid/payload/Android.bp
@@ -25,19 +25,6 @@
     defaults: ["microdroid_metadata_default"],
 }
 
-cc_library_static {
-    name: "lib_microdroid_metadata_proto_lite",
-    recovery_available: true,
-    proto: {
-        export_proto_headers: true,
-        type: "lite",
-    },
-    defaults: ["microdroid_metadata_default"],
-    apex_available: [
-        "com.android.virt",
-    ],
-}
-
 rust_protobuf {
     name: "libmicrodroid_metadata_proto_rust",
     crate_name: "microdroid_metadata",
diff --git a/microdroid/payload/README.md b/microdroid/payload/README.md
index bf05c49..c2f624a 100644
--- a/microdroid/payload/README.md
+++ b/microdroid/payload/README.md
@@ -28,22 +28,20 @@
 
 The partition is a protobuf message prefixed with the size of the message.
 
-| offset | size | description                                                    |
-|--------|------|----------------------------------------------------------------|
-| 0      | 4    | Header. unsigned int32: body length(L) in big endian           |
-| 4      | L    | Body. A protobuf message. [schema](metadata.proto) |
+| offset | size | description                                          |
+| ------ | ---- | ---------------------------------------------------- |
+| 0      | 4    | Header. unsigned int32: body length(L) in big endian |
+| 4      | L    | Body. A protobuf message. [schema](metadata.proto)   |
 
 ### Payload partitions
 
 Each payload partition presents APEX or APK passed from the host.
 
-Note that each payload passed to the Guest is read by a block device. If a payload is not sized to a
-multiples of 4k, reading it would fail. To prevent that, "zero fillers" are added for those files.
-For example, if an APK is 8000 byte big, the APK partition would be padded with 192 bytes of zeros.
+The size of a payload partition must be a multiple of 4096 bytes.
 
 # `mk_payload`
 
-`mk_payload` is a small utility to create a payload disk image.
+`mk_payload` is a small utility to create a payload disk image. It is used by ARCVM.
 
 ```
 $ cat payload_config.json