compos: retrieve fsverity digests of outputs from authfs

In order for compsvc to know which files to sign, it needs to parse the
compiler flags to identify the FD numbers. Once the compiler exits
normally, we retrieve the fs-verity digests of the output files, namely
the oat, vdex and image.

The digests are currently only printed as debug log, but with more
changes to the signing service, we'll be able to sign them easily.

Bug: 161471326
Test: ComposHostTestCases
Test: Visually checks the hex of the fs-verity digests. Looks consistent
      to the digests of the actual files.
Change-Id: I9f7b2cfbb12999b87676b6b8e1bfd199d2a75a6b
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> {