compos: Sign fs-verity digest of output files
compsvc now needs to be initialized with the signing key blob to finish
the whole compilation flow, otherwise it'll be an illegal state error.
"init-key" command is added to compos_key_cmd for that purpose.
Also, move some fs-verity related code into fsverity.rs.
Bug: 161471326
Test: ComposHostTestCases
Change-Id: I80db78ce11dc1f49e9ee36af47ad98f200aa4388
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 24266e6..0199eb5 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -15,15 +15,13 @@
*/
use anyhow::{anyhow, bail, Context, Result};
-use libc::getxattr;
use log::error;
use minijail::{self, Minijail};
-use std::ffi::CString;
use std::fs::File;
-use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::Path;
+use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::{
AuthFsConfig::AuthFsConfig, IAuthFs::IAuthFs, IAuthFsService::IAuthFsService,
InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
@@ -35,12 +33,13 @@
/// 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 },
+ Digests {
+ oat: fsverity::Sha256Digest,
+ vdex: fsverity::Sha256Digest,
+ image: fsverity::Sha256Digest,
+ },
/// Exit code returned by the compiler, if not 0.
ExitCode(i8),
}
@@ -82,9 +81,9 @@
match jail_result {
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())?,
+ 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!("dex2oat failed with exit code {}", exit_code);
@@ -186,22 +185,3 @@
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 b55fb7c..4a19030 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,16 +19,19 @@
//! actual compiler.
use anyhow::Result;
-use log::{debug, warn};
+use log::warn;
+use std::default::Default;
use std::ffi::CString;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use crate::compilation::{compile, CompilerOutput};
use crate::compos_key_service::CompOsKeyService;
+use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
use compos_aidl_interface::aidl::com::android::compos::{
CompOsKeyData::CompOsKeyData,
+ CompilationResult::CompilationResult,
ICompOsService::{BnCompOsService, ICompOsService},
Metadata::Metadata,
};
@@ -55,6 +58,20 @@
key_blob: Arc<RwLock<Vec<u8>>>,
}
+impl CompOsService {
+ fn generate_raw_fsverity_signature(
+ &self,
+ key_blob: &[u8],
+ fsverity_digest: &fsverity::Sha256Digest,
+ ) -> Vec<u8> {
+ let formatted_digest = fsverity::to_formatted_digest(fsverity_digest);
+ self.key_service.do_sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
+ warn!("Failed to sign the fsverity digest, returning empty signature. Error: {}", e);
+ Vec::new()
+ })
+ }
+}
+
impl Interface for CompOsService {}
impl ICompOsService for CompOsService {
@@ -68,7 +85,7 @@
}
}
- fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> {
+ fn compile(&self, args: &[String], metadata: &Metadata) -> BinderResult<CompilationResult> {
let authfs_service = get_authfs_service()?;
let output = compile(&self.dex2oat_path, args, authfs_service, metadata).map_err(|e| {
new_binder_exception(
@@ -78,13 +95,27 @@
})?;
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)
+ let key = &*self.key_blob.read().unwrap();
+ if key.is_empty() {
+ Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_STATE,
+ "Key is not initialized",
+ ))
+ } else {
+ let oat_signature = self.generate_raw_fsverity_signature(key, &oat);
+ let vdex_signature = self.generate_raw_fsverity_signature(key, &vdex);
+ let image_signature = self.generate_raw_fsverity_signature(key, &image);
+ Ok(CompilationResult {
+ exitCode: 0,
+ oatSignature: oat_signature,
+ vdexSignature: vdex_signature,
+ imageSignature: image_signature,
+ })
+ }
}
- CompilerOutput::ExitCode(exit_code) => Ok(exit_code),
+ CompilerOutput::ExitCode(exit_code) => {
+ Ok(CompilationResult { exitCode: exit_code, ..Default::default() })
+ }
}
}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 48e37b6..6396556 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -21,6 +21,7 @@
mod compilation;
mod compos_key_service;
mod compsvc;
+mod fsverity;
mod signer;
use crate::common::{SERVICE_NAME, VSOCK_PORT};
diff --git a/compos/src/fsverity.rs b/compos/src/fsverity.rs
new file mode 100644
index 0000000..a1e2314
--- /dev/null
+++ b/compos/src/fsverity.rs
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::{bail, Result};
+use libc::getxattr;
+use std::ffi::CString;
+use std::io;
+use std::os::unix::io::RawFd;
+
+/// Magic used in fs-verity digest
+const FS_VERITY_MAGIC: &[u8; 8] = b"FSVerity";
+
+/// Hash algorithm to use from linux/fsverity.h
+const FS_VERITY_HASH_ALG_SHA256: u8 = 1;
+
+const SHA256_HASH_SIZE: usize = 32;
+
+/// Size of `struct fsverity_formatted_digest` with SHA-256 in bytes.
+const FORMATTED_SHA256_DIGEST_SIZE: usize = 12 + SHA256_HASH_SIZE;
+
+/// Bytes of `struct fsverity_formatted_digest` in Linux with SHA-256.
+pub type FormattedSha256Digest = [u8; FORMATTED_SHA256_DIGEST_SIZE];
+
+/// Bytes of SHA256 digest
+pub type Sha256Digest = [u8; SHA256_HASH_SIZE];
+
+/// Returns the fs-verity measurement/digest. Currently only SHA256 is supported.
+pub fn measure(fd: RawFd) -> Result<Sha256Digest> {
+ // 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)
+ }
+}
+
+pub fn to_formatted_digest(digest: &Sha256Digest) -> FormattedSha256Digest {
+ let mut formatted_digest: FormattedSha256Digest = [0; FORMATTED_SHA256_DIGEST_SIZE];
+ formatted_digest[0..8].copy_from_slice(FS_VERITY_MAGIC);
+ formatted_digest[8..10].copy_from_slice(&(FS_VERITY_HASH_ALG_SHA256 as u16).to_le_bytes());
+ formatted_digest[10..12].copy_from_slice(&(SHA256_HASH_SIZE as u16).to_le_bytes());
+ formatted_digest[12..].copy_from_slice(digest);
+ formatted_digest
+}
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
index 69eebbf..99eddfc 100644
--- a/compos/src/pvm_exec.rs
+++ b/compos/src/pvm_exec.rs
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-//! pvm_exec is a proxy/wrapper command to run a command remotely. It does not transport the
-//! program and just pass the command line arguments to compsvc to execute. The most important task
+//! pvm_exec is a proxy/wrapper command to run compilation task remotely. The most important task
//! for this program is to run a `fd_server` that serves remote file read/write requests.
//!
-//! Example:
-//! $ adb shell exec 3</dev/zero 4<>/dev/null pvm_exec --in-fd 3 --out-fd 4 -- sleep 10
+//! It currently works as a command line wrapper to make it easy to schedule an existing dex2oat
+//! task to run in the VM.
//!
-//! Note the immediate argument right after "--" (e.g. "sleep" in the example above) is not really
-//! used. It is only for ergonomics.
+//! Example:
+//! $ adb shell exec 3</input/dex 4<>/output/oat ... pvm_exec --in-fd 3 --out-fd 4 -- dex2oat64 ...
+//!
+//! Note the immediate argument "dex2oat64" right after "--" is not really used. It is only for
+//! ergonomics.
use anyhow::{bail, Context, Result};
use binder::unstable_api::{new_spibinder, AIBinder};
use binder::FromIBinder;
-use log::{error, warn};
+use log::{debug, error, warn};
use minijail::Minijail;
use nix::fcntl::{fcntl, FcntlArg::F_GETFD};
use nix::sys::stat::fstat;
@@ -187,14 +189,22 @@
// 3. Send the command line args to the remote to execute.
let service = if let Some(cid) = cid { get_rpc_binder(cid) } else { get_local_service() }?;
- let exit_code = service.execute(&args, &metadata).context("Binder call failed")?;
+ let result = service.compile(&args, &metadata).context("Binder call failed")?;
+
+ // TODO: store/use the signature
+ debug!(
+ "Signature length: oat {}, vdex {}, image {}",
+ result.oatSignature.len(),
+ result.vdexSignature.len(),
+ result.imageSignature.len()
+ );
// Be explicit about the lifetime, which should last at least until the task is finished.
drop(fd_server_lifetime);
- if exit_code > 0 {
- error!("remote execution failed with exit code {}", exit_code);
- exit(exit_code as i32);
+ if result.exitCode > 0 {
+ error!("remote execution failed with exit code {}", result.exitCode);
+ exit(result.exitCode as i32);
}
Ok(())
}