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