Merge "Change the name of the dm device for the correct_custom_roothash test"
diff --git a/apex/Android.bp b/apex/Android.bp
index c568ae2..b1c5190 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -81,4 +81,5 @@
name: "com.android.virt.init.rc",
src: "virtualizationservice.rc",
filename: "init.rc",
+ installable: false,
}
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 5597e22..b240c85 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -240,8 +240,7 @@
}
run_test(modified_apk.as_slice(), idsig.as_ref(), "incorrect_apk", |ctx| {
- let ret = fs::read(&ctx.result.mapper_device).map_err(|e| e.kind());
- assert_eq!(ret, Err(std::io::ErrorKind::Other));
+ fs::read(&ctx.result.mapper_device).expect_err("Should fail");
});
}
@@ -261,8 +260,7 @@
}
run_test(apk.as_ref(), modified_idsig.as_slice(), "incorrect_merkle_tree", |ctx| {
- let ret = fs::read(&ctx.result.mapper_device).map_err(|e| e.kind());
- assert_eq!(ret, Err(std::io::ErrorKind::Other));
+ fs::read(&ctx.result.mapper_device).expect_err("Should fail");
});
}
@@ -284,9 +282,7 @@
// Read around the modified location causes an error
let f = File::open(&ctx.result.mapper_device).unwrap();
let mut buf = vec![0; 10]; // just read 10 bytes
- let ret = f.read_at(&mut buf, MODIFIED_OFFSET).map_err(|e| e.kind());
- assert!(ret.is_err());
- assert_eq!(ret, Err(std::io::ErrorKind::Other));
+ f.read_at(&mut buf, MODIFIED_OFFSET).expect_err("Should fail");
});
}
diff --git a/apkverify/src/lib.rs b/apkverify/src/lib.rs
index f75913c..71ea857 100644
--- a/apkverify/src/lib.rs
+++ b/apkverify/src/lib.rs
@@ -26,8 +26,14 @@
use anyhow::Result;
use std::path::Path;
-/// Verifies APK/APEX signing with v2/v3 scheme
-pub fn verify<P: AsRef<Path>>(path: P) -> Result<()> {
+/// Verifies APK/APEX signing with v2/v3 scheme. On success, the public key (in DER format) is
+/// returned.
+pub fn verify<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
// TODO(jooyung) fallback to v2 when v3 not found
v3::verify(path)
}
+
+/// Gets the public key (in DER format) that was used to sign the given APK/APEX file
+pub fn get_public_key_der<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
+ v3::get_public_key_der(path)
+}
diff --git a/apkverify/src/v3.rs b/apkverify/src/v3.rs
index 2f9ce94..797911b 100644
--- a/apkverify/src/v3.rs
+++ b/apkverify/src/v3.rs
@@ -86,40 +86,50 @@
type X509Certificate = Bytes;
type AdditionalAttributes = Bytes;
-/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
-/// associated with each signer.
-pub fn verify<P: AsRef<Path>>(path: P) -> Result<()> {
+/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the public key
+/// associated with the signer.
+pub fn verify<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
let f = File::open(path.as_ref())?;
let mut sections = ApkSections::new(f)?;
- verify_signature(&mut sections)?;
- Ok(())
+ find_signer_and_then(&mut sections, |(signer, sections)| signer.verify(sections))
}
-/// Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
-/// Block.
-fn verify_signature<R: Read + Seek>(sections: &mut ApkSections<R>) -> Result<()> {
+/// Finds the supported signer and execute a function on it.
+fn find_signer_and_then<R, U, F>(sections: &mut ApkSections<R>, f: F) -> Result<U>
+where
+ R: Read + Seek,
+ F: FnOnce((&Signer, &mut ApkSections<R>)) -> Result<U>,
+{
let mut block = sections.find_signature(APK_SIGNATURE_SCHEME_V3_BLOCK_ID)?;
-
// parse v3 scheme block
let signers = block.read::<Signers>()?;
// find supported by platform
- let mut supported =
- signers.iter().filter(|s| s.sdk_range().contains(&SDK_INT)).collect::<Vec<_>>();
+ let supported = signers.iter().filter(|s| s.sdk_range().contains(&SDK_INT)).collect::<Vec<_>>();
// there should be exactly one
if supported.len() != 1 {
- bail!("APK Signature Scheme V3 only supports one signer: {} signers found.", signers.len())
+ bail!(
+ "APK Signature Scheme V3 only supports one signer: {} signers found.",
+ supported.len()
+ )
}
- // and it should be verified
- supported.pop().unwrap().verify(sections)?;
+ // Call the supplied function
+ f((supported[0], sections))
+}
- Ok(())
+/// Gets the public key (in DER format) that was used to sign the given APK/APEX file
+pub fn get_public_key_der<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
+ let f = File::open(path.as_ref())?;
+ let mut sections = ApkSections::new(f)?;
+ find_signer_and_then(&mut sections, |(signer, _)| {
+ Ok(signer.public_key.to_vec().into_boxed_slice())
+ })
}
impl Signer {
- fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<()> {
+ fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<Box<[u8]>> {
// 1. Choose the strongest supported signature algorithm ID from signatures. The strength
// ordering is up to each implementation/platform version.
let strongest: &Signature = self
@@ -181,7 +191,7 @@
}
// TODO(jooyung) 8. If the proof-of-rotation attribute exists for the signer verify that the struct is valid and this signer is the last certificate in the list.
- Ok(())
+ Ok(self.public_key.to_vec().into_boxed_slice())
}
}
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 853b9f4..ada0976 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -40,6 +40,7 @@
binaries: [
"compos_key_cmd",
"compos_verify_key",
+ "composd",
"compsvc",
"pvm_exec",
],
@@ -50,5 +51,13 @@
prebuilts: [
"CompOSPayloadApp.apk.idsig",
+ "com.android.compos.init.rc",
],
}
+
+prebuilt_etc {
+ name: "com.android.compos.init.rc",
+ src: "composd.rc",
+ filename: "init.rc",
+ installable: false,
+}
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
new file mode 100644
index 0000000..099a346
--- /dev/null
+++ b/compos/apex/composd.rc
@@ -0,0 +1,21 @@
+# 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.
+
+service composd /apex/com.android.compos/bin/composd
+ class main
+ user system
+ group system
+ interface aidl android.system.composd
+ disabled
+ oneshot
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index e0584f4..36c1b5c 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -15,8 +15,8 @@
"android.system.virtualizationservice-ndk",
"compos_aidl_interface-ndk",
"libbase",
- "libbinder_rpc_unstable",
"libbinder_ndk",
+ "libbinder_rpc_unstable",
"libcrypto",
"libfsverity",
"libprotobuf-cpp-lite",
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index eb11d92..ff53548 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -33,6 +33,7 @@
#include <openssl/x509.h>
#include <unistd.h>
+#include <binder_rpc_unstable.hpp>
#include <chrono>
#include <condition_variable>
#include <filesystem>
@@ -43,11 +44,6 @@
#include "compos_signature.pb.h"
-// From frameworks/native/libs/binder/rust/src/binder_rpc_unstable.hpp
-extern "C" {
-AIBinder* RpcClient(unsigned int cid, unsigned int port);
-}
-
using namespace std::literals;
using aidl::android::system::virtualizationservice::BnVirtualMachineCallback;
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
new file mode 100644
index 0000000..007eda9
--- /dev/null
+++ b/compos/composd/Android.bp
@@ -0,0 +1,22 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "composd",
+ srcs: ["src/composd_main.rs"],
+ edition: "2018",
+ rustlibs: [
+ "android.system.composd-rust",
+ "compos_aidl_interface-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "libbinder_rs",
+ "libcompos_common",
+ "liblog_rust",
+ ],
+ prefer_rlib: true,
+ apex_available: [
+ "com.android.compos",
+ ],
+}
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
new file mode 100644
index 0000000..90c0de0
--- /dev/null
+++ b/compos/composd/aidl/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "android.system.composd",
+ srcs: ["android/system/composd/*.aidl"],
+ // TODO: Make this stable when the APEX becomes updatable.
+ unstable: true,
+ backend: {
+ java: {
+ apex_available: ["//apex_available:platform"],
+ },
+ rust: {
+ enabled: true,
+ apex_available: [
+ "com.android.compos",
+ ],
+ },
+ },
+}
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
new file mode 100644
index 0000000..0dd5b6f
--- /dev/null
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+package android.system.composd;
+
+interface IIsolatedCompilationService {
+ // TODO: Add real methods
+ void doSomething();
+}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
new file mode 100644
index 0000000..f674448
--- /dev/null
+++ b/compos/composd/src/composd_main.rs
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+//! Exposes an on-demand binder service to perform system compilation tasks using CompOS. It is
+//! responsible for managing the lifecycle of the CompOS VM instances, providing key management for
+//! them, and orchestrating trusted compilation.
+
+mod service;
+
+use android_system_composd::binder::{register_lazy_service, ProcessState};
+use anyhow::{Context, Result};
+use log::{error, info};
+
+fn try_main() -> Result<()> {
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("composd").with_min_level(log::Level::Info),
+ );
+
+ let service = service::new_binder();
+ register_lazy_service("android.system.composd", service.as_binder())
+ .context("Registering service")?;
+
+ info!("Registered service, joining threadpool");
+ ProcessState::join_thread_pool();
+
+ info!("Exiting");
+ Ok(())
+}
+
+fn main() {
+ if let Err(e) = try_main() {
+ error!("{}", e);
+ std::process::exit(1)
+ }
+}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
new file mode 100644
index 0000000..8fe28ec
--- /dev/null
+++ b/compos/composd/src/service.rs
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+//! Implementation of IIsolatedCompilationService, called from system server when compilation is
+//! desired.
+
+use android_system_composd::aidl::android::system::composd::IIsolatedCompilationService::{
+ BnIsolatedCompilationService, IIsolatedCompilationService,
+};
+use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
+
+pub struct IsolatedCompilationService {}
+
+pub fn new_binder() -> Strong<dyn IIsolatedCompilationService> {
+ let service = IsolatedCompilationService {};
+ BnIsolatedCompilationService::new_binder(service, BinderFeatures::default())
+}
+
+impl IsolatedCompilationService {}
+
+impl Interface for IsolatedCompilationService {}
+
+impl IIsolatedCompilationService for IsolatedCompilationService {
+ fn doSomething(&self) -> binder::Result<()> {
+ Ok(())
+ }
+}
diff --git a/libs/libavb_rs/Android.bp b/libs/libavb_rs/Android.bp
new file mode 100644
index 0000000..1035498
--- /dev/null
+++ b/libs/libavb_rs/Android.bp
@@ -0,0 +1,31 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+ name: "libavb_bindgen",
+ wrapper_src: "bindgen/avb.h",
+ crate_name: "avb_bindgen",
+ source_stem: "bindings",
+ bindgen_flags: [
+ "--size_t-is-usize",
+ "--allowlist-function=.*",
+ ],
+ static_libs: [
+ "libavb",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ cflags: ["-DBORINGSSL_NO_CXX"],
+}
+
+rust_test {
+ name: "libavb_bindgen_test",
+ srcs: [":libavb_bindgen"],
+ crate_name: "avb_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
diff --git a/libs/libavb_rs/bindgen/avb.h b/libs/libavb_rs/bindgen/avb.h
new file mode 100644
index 0000000..b3d5385
--- /dev/null
+++ b/libs/libavb_rs/bindgen/avb.h
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <libavb/libavb.h>
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 4c32dde..5ae2158 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -31,14 +31,13 @@
message ApexPayload {
// Required.
- // The apex name.
string name = 1;
-
string partition_name = 2;
// Optional.
- // When specified, the public key used to sign the apex should match with it.
+ // When specified, apex payload should be verified with the public key and root digest.
bytes public_key = 3;
+ bytes root_digest = 4;
}
message ApkPayload {
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 9957689..721f9fa 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -13,6 +13,7 @@
"android.system.virtualmachineservice-rust",
"libanyhow",
"libapkverify",
+ "libavb_bindgen",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"libbyteorder",
@@ -63,4 +64,5 @@
enabled: false,
},
},
+ data: ["tests/data/*"],
}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 73983a7..47230e3 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -320,7 +320,7 @@
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ApkData {
pub root_hash: Box<RootHash>,
- // TODO(b/199143508) add cert
+ pub pubkey: Box<[u8]>,
}
pub type RootHash = [u8];
@@ -328,5 +328,6 @@
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ApexData {
pub name: String,
- pub pubkey: Vec<u8>,
+ pub public_key: Vec<u8>,
+ pub root_digest: Vec<u8>,
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 204feab..4953a4c 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -20,7 +20,7 @@
use crate::instance::{ApkData, InstanceDisk, MicrodroidData, RootHash};
use anyhow::{anyhow, bail, ensure, Context, Result};
-use apkverify::verify;
+use apkverify::{get_public_key_der, verify};
use binder::unstable_api::{new_spibinder, AIBinder};
use binder::{FromIBinder, Strong};
use idsig::V4Signature;
@@ -162,14 +162,15 @@
// Start apkdmverity and wait for the dm-verify block
system_properties::write("ctl.start", "apkdmverity")?;
- // While waiting for apkdmverity to mount APK, gathers APEX pubkeys from payload.
+ // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
+ // APEX payload.
let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
if let Some(saved_data) = saved_data.map(|d| &d.apex_data) {
- // For APEX payload, we don't support updating their pubkeys
+ // We don't support APEX updates. (assuming that update will change root digest)
ensure!(saved_data == &apex_data_from_payload, "APEX payloads has changed.");
let apex_metadata = to_metadata(&apex_data_from_payload);
- // Pass metadata(with pubkeys) to apexd so that it uses the passed metadata
- // instead of the default one (/dev/block/by-name/payload-metadata)
+ // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
+ // metadata instead of the default one (/dev/block/by-name/payload-metadata)
OpenOptions::new()
.create_new(true)
.write(true)
@@ -187,16 +188,18 @@
// taken only when the root_hash is un-trustful which can be either when this is the first boot
// of the VM or APK was updated in the host.
// TODO(jooyung): consider multithreading to make this faster
- if !root_hash_trustful {
- verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?;
- }
+ let apk_pubkey = if !root_hash_trustful {
+ verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?
+ } else {
+ get_public_key_der(DM_MOUNTED_APK_PATH)?
+ };
info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
// At this point, we can ensure that the root_hash from the idsig file is trusted, either by
// fully verifying the APK or by comparing it with the saved root_hash.
Ok(MicrodroidData {
- apk_data: ApkData { root_hash: root_hash_from_idsig },
+ apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: apk_pubkey },
apex_data: apex_data_from_payload,
})
}
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
index bf9d9f9..8ec6f74 100644
--- a/microdroid_manager/src/payload.rs
+++ b/microdroid_manager/src/payload.rs
@@ -17,14 +17,11 @@
use crate::instance::ApexData;
use crate::ioutil::wait_for_file;
use anyhow::Result;
+use apex::verify;
use log::info;
use microdroid_metadata::{read_metadata, ApexPayload, Metadata};
-use std::fs::File;
-use std::io::Read;
use std::time::Duration;
-use zip::ZipArchive;
-const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
const PAYLOAD_METADATA_PATH: &str = "/dev/block/by-name/payload-metadata";
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -35,29 +32,20 @@
read_metadata(file)
}
-/// Loads (name, pubkey) from payload APEXes
+/// Loads (name, public_key, root_digest) from payload APEXes
pub fn get_apex_data_from_payload(metadata: &Metadata) -> Result<Vec<ApexData>> {
metadata
.apexes
.iter()
.map(|apex| {
let name = apex.name.clone();
- let partition = format!("/dev/block/by-name/{}", apex.partition_name);
- let pubkey = get_pubkey_from_apex(&partition)?;
- Ok(ApexData { name, pubkey })
+ let apex_path = format!("/dev/block/by-name/{}", apex.partition_name);
+ let result = verify(&apex_path)?;
+ Ok(ApexData { name, public_key: result.public_key, root_digest: result.root_digest })
})
.collect()
}
-fn get_pubkey_from_apex(path: &str) -> Result<Vec<u8>> {
- let f = File::open(path)?;
- let mut z = ZipArchive::new(f)?;
- let mut pubkey_file = z.by_name(APEX_PUBKEY_ENTRY)?;
- let mut pubkey = Vec::new();
- pubkey_file.read_to_end(&mut pubkey)?;
- Ok(pubkey)
-}
-
/// Convert vector of ApexData into Metadata
pub fn to_metadata(apex_data: &[ApexData]) -> Metadata {
Metadata {
@@ -65,10 +53,13 @@
.iter()
.map(|data| ApexPayload {
name: data.name.clone(),
- public_key: data.pubkey.clone(),
+ public_key: data.public_key.clone(),
+ root_digest: data.root_digest.clone(),
..Default::default()
})
.collect(),
..Default::default()
}
}
+
+mod apex;
diff --git a/microdroid_manager/src/payload/apex.rs b/microdroid_manager/src/payload/apex.rs
new file mode 100644
index 0000000..24c4f05
--- /dev/null
+++ b/microdroid_manager/src/payload/apex.rs
@@ -0,0 +1,225 @@
+// Copyright 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.
+
+//! Routines for handling APEX payload
+
+use anyhow::{anyhow, ensure, Result};
+use avb_bindgen::*;
+use std::ffi::{c_void, CStr};
+use std::fs::File;
+use std::io::{Read, Seek, SeekFrom};
+use std::mem::{size_of, zeroed};
+use std::ops::Deref;
+use std::ptr::null_mut;
+use std::slice::{from_raw_parts, from_raw_parts_mut};
+use zip::ZipArchive;
+
+const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
+const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
+
+/// Verification result holds public key and root digest of apex_payload.img
+pub struct ApexVerificationResult {
+ pub public_key: Vec<u8>,
+ pub root_digest: Vec<u8>,
+}
+
+/// Verify APEX payload by AVB verification and return public key and root digest
+pub fn verify(path: &str) -> Result<ApexVerificationResult> {
+ let apex_file = File::open(path)?;
+ let (public_key, image_offset, image_size) = get_public_key_and_image_info(&apex_file)?;
+ let root_digest = verify_vbmeta(apex_file, image_offset, image_size, &public_key)?;
+ Ok(ApexVerificationResult { public_key, root_digest })
+}
+
+fn get_public_key_and_image_info(apex_file: &File) -> Result<(Vec<u8>, u64, u64)> {
+ let mut z = ZipArchive::new(apex_file)?;
+
+ let mut public_key = Vec::new();
+ z.by_name(APEX_PUBKEY_ENTRY)?.read_to_end(&mut public_key)?;
+
+ let (image_offset, image_size) =
+ z.by_name(APEX_PAYLOAD_ENTRY).map(|f| (f.data_start(), f.size()))?;
+
+ Ok((public_key, image_offset, image_size))
+}
+
+// Manual addition of a missing enum
+#[allow(non_camel_case_types, dead_code)]
+#[repr(u8)]
+enum AvbDescriptorTag {
+ AVB_DESCRIPTOR_TAG_PROPERTY = 0,
+ AVB_DESCRIPTOR_TAG_HASHTREE,
+ AVB_DESCRIPTOR_TAG_HASH,
+ AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+ AVB_DESCRIPTOR_TAG_CHAIN_PARTITION,
+}
+
+const FOOTER_SIZE: usize = size_of::<AvbFooter>();
+const HASHTREE_DESCRIPTOR_SIZE: usize = size_of::<AvbHashtreeDescriptor>();
+
+/// Verify VBmeta image and return root digest
+fn verify_vbmeta<R: Read + Seek>(
+ image: R,
+ offset: u64,
+ size: u64,
+ public_key: &[u8],
+) -> Result<Vec<u8>> {
+ let vbmeta = VbMeta::from(image, offset, size)?;
+ vbmeta.verify(public_key)?;
+ for &descriptor in vbmeta.descriptors()?.iter() {
+ if let Ok(hashtree_descriptor) = HashtreeDescriptor::from(descriptor) {
+ return hashtree_descriptor.root_digest();
+ }
+ }
+ Err(anyhow!("HashtreeDescriptor is not found."))
+}
+
+struct VbMeta {
+ data: Vec<u8>,
+}
+
+impl VbMeta {
+ // Read a VbMeta data from a given image
+ fn from<R: Read + Seek>(mut image: R, offset: u64, size: u64) -> Result<VbMeta> {
+ // Get AvbFooter first
+ image.seek(SeekFrom::Start(offset + size - FOOTER_SIZE as u64))?;
+ // SAFETY: AvbDescriptor is a "repr(C,packed)" struct from bindgen
+ let mut footer: AvbFooter = unsafe { zeroed() };
+ // SAFETY: safe to read because of seek(-FOOTER_SIZE) above
+ unsafe {
+ let footer_slice = from_raw_parts_mut(&mut footer as *mut _ as *mut u8, FOOTER_SIZE);
+ image.read_exact(footer_slice)?;
+ ensure!(avb_footer_validate_and_byteswap(&footer, &mut footer));
+ }
+ // Get VbMeta block
+ image.seek(SeekFrom::Start(offset + footer.vbmeta_offset))?;
+ let vbmeta_size = footer.vbmeta_size as usize;
+ let mut data = vec![0u8; vbmeta_size];
+ image.read_exact(&mut data)?;
+ Ok(VbMeta { data })
+ }
+ // Verify VbMeta image. Its enclosed public key should match with a given public key.
+ fn verify(&self, outer_public_key: &[u8]) -> Result<()> {
+ // SAFETY: self.data points to a valid VBMeta data and avb_vbmeta_image_verify should work fine
+ // with it
+ let public_key = unsafe {
+ let mut pk_ptr: *const u8 = null_mut();
+ let mut pk_len: usize = 0;
+ let res = avb_vbmeta_image_verify(
+ self.data.as_ptr(),
+ self.data.len(),
+ &mut pk_ptr,
+ &mut pk_len,
+ );
+ ensure!(
+ res == AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK,
+ CStr::from_ptr(avb_vbmeta_verify_result_to_string(res))
+ .to_string_lossy()
+ .into_owned()
+ );
+ from_raw_parts(pk_ptr, pk_len)
+ };
+
+ ensure!(public_key == outer_public_key, "Public key mismatch with a given one.");
+ Ok(())
+ }
+ // Return a slice of AvbDescriptor pointers
+ fn descriptors(&self) -> Result<Descriptors> {
+ let mut num: usize = 0;
+ // SAFETY: ptr will be freed by Descriptor.
+ Ok(unsafe {
+ let ptr = avb_descriptor_get_all(self.data.as_ptr(), self.data.len(), &mut num);
+ ensure!(!ptr.is_null(), "VbMeta has no descriptors.");
+ let all = from_raw_parts(ptr, num);
+ Descriptors { ptr, all }
+ })
+ }
+}
+
+struct HashtreeDescriptor {
+ ptr: *const u8,
+ inner: AvbHashtreeDescriptor,
+}
+
+impl HashtreeDescriptor {
+ fn from(descriptor: *const AvbDescriptor) -> Result<HashtreeDescriptor> {
+ // SAFETY: AvbDescriptor is a "repr(C,packed)" struct from bindgen
+ let mut desc: AvbDescriptor = unsafe { zeroed() };
+ // SAFETY: both points to valid AvbDescriptor pointers
+ unsafe {
+ ensure!(avb_descriptor_validate_and_byteswap(descriptor, &mut desc));
+ }
+ ensure!({ desc.tag } == AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASHTREE as u64);
+ // SAFETY: AvbHashtreeDescriptor is a "repr(C, packed)" struct from bindgen
+ let mut hashtree_descriptor: AvbHashtreeDescriptor = unsafe { zeroed() };
+ // SAFETY: With tag == AVB_DESCRIPTOR_TAG_HASHTREE, descriptor should point to
+ // a AvbHashtreeDescriptor.
+ unsafe {
+ ensure!(avb_hashtree_descriptor_validate_and_byteswap(
+ descriptor as *const AvbHashtreeDescriptor,
+ &mut hashtree_descriptor,
+ ));
+ }
+ Ok(Self { ptr: descriptor as *const u8, inner: hashtree_descriptor })
+ }
+ fn root_digest(&self) -> Result<Vec<u8>> {
+ // SAFETY: digest_ptr should point to a valid buffer of root_digest_len
+ let root_digest = unsafe {
+ let digest_ptr = self.ptr.offset(
+ HASHTREE_DESCRIPTOR_SIZE as isize
+ + self.inner.partition_name_len as isize
+ + self.inner.salt_len as isize,
+ );
+ from_raw_parts(digest_ptr, self.inner.root_digest_len as usize)
+ };
+ Ok(root_digest.to_owned())
+ }
+}
+
+// Wraps pointer to a heap-allocated array of AvbDescriptor pointers
+struct Descriptors<'a> {
+ ptr: *mut *const AvbDescriptor,
+ all: &'a [*const AvbDescriptor],
+}
+
+// Wrapped pointer should be freed with avb_free.
+impl Drop for Descriptors<'_> {
+ fn drop(&mut self) {
+ // SAFETY: ptr is allocated by avb_descriptor_get_all
+ unsafe { avb_free(self.ptr as *mut c_void) }
+ }
+}
+
+impl<'a> Deref for Descriptors<'a> {
+ type Target = &'a [*const AvbDescriptor];
+ fn deref(&self) -> &Self::Target {
+ &self.all
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ fn to_hex_string(buf: &[u8]) -> String {
+ buf.iter().map(|b| format!("{:02x}", b)).collect()
+ }
+ #[test]
+ fn test_open_apex() {
+ let res = verify("tests/data/test.apex").unwrap();
+ assert_eq!(
+ to_hex_string(&res.root_digest),
+ "fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
+ );
+ }
+}
diff --git a/microdroid_manager/tests/data/README.md b/microdroid_manager/tests/data/README.md
new file mode 100644
index 0000000..82ebec6
--- /dev/null
+++ b/microdroid_manager/tests/data/README.md
@@ -0,0 +1,3 @@
+# Test data
+
+- test.apex: copied from system/apexshim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
\ No newline at end of file
diff --git a/microdroid_manager/tests/data/test.apex b/microdroid_manager/tests/data/test.apex
new file mode 100644
index 0000000..fd79365
--- /dev/null
+++ b/microdroid_manager/tests/data/test.apex
Binary files differ
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index b0ea0ba..449103e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -35,7 +35,7 @@
VirtualMachineState::VirtualMachineState,
};
use android_system_virtualizationservice::binder::{
- self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
+ self, force_lazy_services_persist, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, BnVirtualMachineService, IVirtualMachineService,
@@ -716,12 +716,20 @@
/// Store a strong VM reference.
fn debug_hold_vm(&mut self, vm: Strong<dyn IVirtualMachine>) {
self.debug_held_vms.push(vm);
+ // Make sure our process is not shut down while we hold the VM reference
+ // on behalf of the caller.
+ force_lazy_services_persist(true);
}
/// Retrieve and remove a strong VM reference.
fn debug_drop_vm(&mut self, cid: i32) -> Option<Strong<dyn IVirtualMachine>> {
let pos = self.debug_held_vms.iter().position(|vm| vm.getCid() == Ok(cid))?;
- Some(self.debug_held_vms.swap_remove(pos))
+ let vm = self.debug_held_vms.swap_remove(pos);
+ if self.debug_held_vms.is_empty() {
+ // Once we no longer hold any VM references it is ok for our process to be shut down.
+ force_lazy_services_persist(false);
+ }
+ Some(vm)
}
/// Get the next available CID, or an error if we have run out.
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 8628c01..22ddf08 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -21,7 +21,7 @@
use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
-use android_system_virtualizationservice::binder::{add_service, BinderFeatures, ProcessState};
+use android_system_virtualizationservice::binder::{register_lazy_service, BinderFeatures, ProcessState};
use anyhow::Error;
use log::{info, Level};
use std::fs::{remove_dir_all, remove_file, read_dir};
@@ -47,7 +47,7 @@
service,
BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
);
- add_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
+ register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
info!("Registered Binder service, joining threadpool.");
ProcessState::join_thread_pool();
}