Merge "Add ABL-compliant pvmfw pre-built"
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 8ddbf69..327df0d 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -9,6 +9,7 @@
"authfs_aidl_interface-rust",
"libandroid_logger",
"libanyhow",
+ "libbinder_common",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"libclap",
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 7e551a3..32ffc0a 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -30,9 +30,9 @@
use std::cmp::min;
use std::collections::BTreeMap;
use std::convert::TryInto;
-use std::ffi::CString;
use std::fs::File;
use std::io;
+use std::os::raw;
use std::os::unix::fs::FileExt;
use std::os::unix::io::{AsRawFd, FromRawFd};
@@ -43,13 +43,10 @@
use authfs_aidl_interface::binder::{
BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
};
+use binder_common::new_binder_exception;
const RPC_SERVICE_PORT: u32 = 3264; // TODO: support dynamic port for multiple fd_server instances
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
- Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
-
fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
offset.try_into().map_err(|_| {
new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, format!("Invalid offset: {}", offset))
@@ -296,7 +293,12 @@
Ok((fd, FdConfig::ReadWrite(file)))
}
-fn parse_args() -> Result<BTreeMap<i32, FdConfig>> {
+struct Args {
+ fd_pool: BTreeMap<i32, FdConfig>,
+ ready_fd: Option<File>,
+}
+
+fn parse_args() -> Result<Args> {
#[rustfmt::skip]
let matches = clap::App::new("fd_server")
.arg(clap::Arg::with_name("ro-fds")
@@ -307,6 +309,9 @@
.long("rw-fds")
.multiple(true)
.number_of_values(1))
+ .arg(clap::Arg::with_name("ready-fd")
+ .long("ready-fd")
+ .takes_value(true))
.get_matches();
let mut fd_pool = BTreeMap::new();
@@ -322,8 +327,13 @@
fd_pool.insert(fd, config);
}
}
-
- Ok(fd_pool)
+ let ready_fd = if let Some(arg) = matches.value_of("ready-fd") {
+ let fd = arg.parse::<i32>()?;
+ Some(fd_to_file(fd)?)
+ } else {
+ None
+ };
+ Ok(Args { fd_pool, ready_fd })
}
fn main() -> Result<()> {
@@ -331,16 +341,22 @@
android_logger::Config::default().with_tag("fd_server").with_min_level(log::Level::Debug),
);
- let fd_pool = parse_args()?;
+ let args = parse_args()?;
- let mut service = FdService::new_binder(fd_pool).as_binder();
+ let mut service = FdService::new_binder(args.fd_pool).as_binder();
+ let mut ready_notifier = ReadyNotifier(args.ready_fd);
+
debug!("fd_server is starting as a rpc service.");
// SAFETY: Service ownership is transferring to the server and won't be valid afterward.
// Plus the binder objects are threadsafe.
+ // RunRpcServerCallback does not retain a reference to ready_callback, and only ever
+ // calls it with the param we provide during the lifetime of ready_notifier.
let retval = unsafe {
- binder_rpc_unstable_bindgen::RunRpcServer(
+ binder_rpc_unstable_bindgen::RunRpcServerCallback(
service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
RPC_SERVICE_PORT,
+ Some(ReadyNotifier::ready_callback),
+ ready_notifier.as_void_ptr(),
)
};
if retval {
@@ -350,3 +366,25 @@
bail!("Premature termination of RPC server");
}
}
+
+struct ReadyNotifier(Option<File>);
+
+impl ReadyNotifier {
+ fn notify(&mut self) {
+ debug!("fd_server is ready");
+ // Close the ready-fd if we were given one to signal our readiness.
+ drop(self.0.take());
+ }
+
+ fn as_void_ptr(&mut self) -> *mut raw::c_void {
+ self as *mut _ as *mut raw::c_void
+ }
+
+ unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
+ // SAFETY: This is only ever called by RunRpcServerCallback, within the lifetime of the
+ // ReadyNotifier, with param taking the value returned by as_void_ptr (so a properly aligned
+ // non-null pointer to an initialized instance).
+ let ready_notifier = param as *mut Self;
+ ready_notifier.as_mut().unwrap().notify()
+ }
+}
diff --git a/authfs/service/Android.bp b/authfs/service/Android.bp
index 943db35..6c32c67 100644
--- a/authfs/service/Android.bp
+++ b/authfs/service/Android.bp
@@ -12,6 +12,7 @@
"authfs_aidl_interface-rust",
"libandroid_logger",
"libanyhow",
+ "libbinder_common",
"libbinder_rs",
"liblibc",
"liblog_rust",
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
index f41a3a6..6d87243 100644
--- a/authfs/service/src/authfs.rs
+++ b/authfs/service/src/authfs.rs
@@ -26,7 +26,6 @@
use std::thread::sleep;
use std::time::{Duration, Instant};
-use crate::common::new_binder_exception;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::{BnAuthFs, IAuthFs};
use authfs_aidl_interface::aidl::com::android::virt::fs::{
AuthFsConfig::AuthFsConfig, InputFdAnnotation::InputFdAnnotation,
@@ -35,6 +34,7 @@
use authfs_aidl_interface::binder::{
self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Strong,
};
+use binder_common::new_binder_exception;
const AUTHFS_BIN: &str = "/system/bin/authfs";
const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
@@ -82,8 +82,11 @@
&config.outputFdAnnotations,
debuggable,
)?;
- wait_until_authfs_ready(&mountpoint).map_err(|e| {
- debug!("Wait for authfs: {:?}", child.wait());
+ wait_until_authfs_ready(&child, &mountpoint).map_err(|e| {
+ match child.wait() {
+ Ok(status) => debug!("Wait for authfs: {}", status),
+ Err(e) => warn!("Failed to wait for child: {}", e),
+ }
e
})?;
@@ -144,13 +147,18 @@
SharedChild::spawn(&mut command).context("Spawn authfs")
}
-fn wait_until_authfs_ready(mountpoint: &OsStr) -> Result<()> {
+fn wait_until_authfs_ready(child: &SharedChild, mountpoint: &OsStr) -> Result<()> {
let start_time = Instant::now();
loop {
if is_fuse(mountpoint)? {
break;
}
+ if let Some(exit_status) = child.try_wait()? {
+ // If the child has exited, we will never become ready.
+ bail!("Child has exited: {}", exit_status);
+ }
if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
+ let _ = child.kill();
bail!("Time out mounting authfs");
}
sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index e426734..890e108 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -21,7 +21,6 @@
//! is able to retrieve "remote file descriptors".
mod authfs;
-mod common;
use anyhow::{bail, Context, Result};
use log::*;
@@ -29,7 +28,6 @@
use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
use std::sync::atomic::{AtomicUsize, Ordering};
-use crate::common::new_binder_exception;
use authfs_aidl_interface::aidl::com::android::virt::fs::AuthFsConfig::AuthFsConfig;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::IAuthFs;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::{
@@ -38,6 +36,7 @@
use authfs_aidl_interface::binder::{
self, add_service, BinderFeatures, ExceptionCode, Interface, ProcessState, Strong,
};
+use binder_common::new_binder_exception;
const SERVICE_NAME: &str = "authfs_service";
const SERVICE_ROOT: &str = "/data/misc/authfs";
@@ -60,7 +59,7 @@
create_dir(&mountpoint).map_err(|e| {
new_binder_exception(
ExceptionCode::SERVICE_SPECIFIC,
- format!("Cannot create mount directory {:?}: {}", &mountpoint, e),
+ format!("Cannot create mount directory {:?}: {:?}", &mountpoint, e),
)
})?;
@@ -110,7 +109,7 @@
Ok(())
}
-fn main() -> Result<()> {
+fn try_main() -> Result<()> {
let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
android_logger::init_once(
@@ -129,3 +128,10 @@
ProcessState::join_thread_pool();
bail!("Unexpected exit after join_thread_pool")
}
+
+fn main() {
+ if let Err(e) = try_main() {
+ error!("failed with {:?}", e);
+ std::process::exit(1);
+ }
+}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index c85d801..ecb0e68 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -28,6 +28,7 @@
//! e.g. /mountpoint/42.
use anyhow::{bail, Context, Result};
+use log::error;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::fs::File;
@@ -325,7 +326,7 @@
Ok(file_pool)
}
-fn main() -> Result<()> {
+fn try_main() -> Result<()> {
let args = Args::from_args();
let log_level = if args.debug { log::Level::Debug } else { log::Level::Info };
@@ -337,3 +338,10 @@
fusefs::loop_forever(file_pool, &args.mount_point, &args.extra_options)?;
bail!("Unexpected exit after the handler loop")
}
+
+fn main() {
+ if let Err(e) = try_main() {
+ error!("failed with {:?}", e);
+ std::process::exit(1);
+ }
+}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 1b4fa4a..3d97ee7 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -44,6 +44,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
@RootPermissionTest
@RunWith(DeviceJUnit4ClassRunner.class)
@@ -67,6 +68,8 @@
/** FUSE's magic from statfs(2) */
private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
+ private static final int VMADDR_CID_HOST = 2;
+
private static CommandRunner sAndroid;
private static String sCid;
private static boolean sAssumptionFailed;
@@ -108,7 +111,8 @@
apkName,
packageName,
configPath,
- /* debug */ false);
+ /* debug */ false,
+ /* use default memoryMib */ 0);
adbConnectToMicrodroid(androidDevice, sCid);
// Root because authfs (started from shell in this test) currently require root to open
@@ -154,7 +158,8 @@
"--ro-fds 3:4:5 --ro-fds 6");
runAuthFsOnMicrodroid(
- "--remote-ro-file-unverified 10:6 --remote-ro-file 11:3:cert.der --cid 2");
+ "--remote-ro-file-unverified 10:6 --remote-ro-file 11:3:cert.der --cid "
+ + VMADDR_CID_HOST);
// Action
String actualHashUnverified4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
@@ -178,7 +183,8 @@
+ " 6<input.4k1 7<input.4k1.merkle_dump 8<input.4k1.fsv_sig",
"--ro-fds 3:4:5 --ro-fds 6:7:8");
runAuthFsOnMicrodroid(
- "--remote-ro-file 10:3:cert.der --remote-ro-file 11:6:cert.der --cid 2");
+ "--remote-ro-file 10:3:cert.der --remote-ro-file 11:6:cert.der --cid "
+ + VMADDR_CID_HOST);
// Action
String actualHash4k = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
@@ -198,7 +204,7 @@
// Setup
runFdServerOnAndroid(
"3<input.4m 4<input.4m.merkle_dump.bad 5<input.4m.fsv_sig", "--ro-fds 3:4:5");
- runAuthFsOnMicrodroid("--remote-ro-file 10:3:cert.der --cid 2");
+ runAuthFsOnMicrodroid("--remote-ro-file 10:3:cert.der --cid " + VMADDR_CID_HOST);
// Verify
assertFalse(copyFileOnMicrodroid(MOUNT_DIR + "/10", "/dev/null"));
@@ -209,7 +215,7 @@
throws DeviceNotAvailableException, InterruptedException {
// Setup
runFdServerOnAndroid("3<>output", "--rw-fds 3");
- runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
// Action
String srcPath = "/system/bin/linker64";
@@ -227,7 +233,7 @@
throws DeviceNotAvailableException, InterruptedException {
// Setup
runFdServerOnAndroid("3<>output", "--rw-fds 3");
- runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
String srcPath = "/system/bin/linker64";
String destPath = MOUNT_DIR + "/20";
@@ -258,7 +264,7 @@
public void testFileResize() throws DeviceNotAvailableException, InterruptedException {
// Setup
runFdServerOnAndroid("3<>output", "--rw-fds 3");
- runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
String outputPath = MOUNT_DIR + "/20";
String backendPath = TEST_DIR + "/output";
@@ -337,11 +343,16 @@
private void runAuthFsOnMicrodroid(String flags) {
String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
+ AtomicBoolean starting = new AtomicBoolean(true);
mThreadPool.submit(
() -> {
- CLog.i("Starting authfs");
- CommandResult result = runOnMicrodroidForResult(cmd);
- CLog.w("authfs has stopped: " + result);
+ // authfs may fail to start if fd_server is not yet listening on the vsock
+ // ("Error: Invalid raw AIBinder"). Just restart if that happens.
+ while (starting.get()) {
+ CLog.i("Starting authfs");
+ CommandResult result = runOnMicrodroidForResult(cmd);
+ CLog.w("authfs has stopped: " + result);
+ }
});
try {
PollingCheck.waitFor(
@@ -351,6 +362,8 @@
// methods. waitFor throws Exception because the callback, Callable#call(), has a
// signature to throw an Exception.
throw new RuntimeException(e);
+ } finally {
+ starting.set(false);
}
}
diff --git a/binder_common/Android.bp b/binder_common/Android.bp
new file mode 100644
index 0000000..9e6d590
--- /dev/null
+++ b/binder_common/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libbinder_common",
+ crate_name: "binder_common",
+ srcs: ["lib.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libbinder_rs",
+ "liblazy_static",
+ ],
+ apex_available: [
+ "com.android.compos",
+ "com.android.virt",
+ ],
+}
diff --git a/binder_common/lazy_service.rs b/binder_common/lazy_service.rs
new file mode 100644
index 0000000..a2b85db
--- /dev/null
+++ b/binder_common/lazy_service.rs
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+//! Rust API for lazy (aka dynamic) AIDL services.
+//! See https://source.android.com/devices/architecture/aidl/dynamic-aidl.
+
+use binder::public_api::force_lazy_services_persist;
+use lazy_static::lazy_static;
+use std::sync::Mutex;
+
+// TODO(b/200924402): Move this class to libbinder_rs once the infrastructure needed exists.
+
+/// An RAII object to ensure a server of lazy services is not killed. During the lifetime of any of
+/// these objects the service manager will not not kill the current process even if none of its
+/// lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+ // Prevent construction outside this module.
+ _private: (),
+}
+
+lazy_static! {
+ // Count of how many LazyServiceGuard objects are in existence.
+ static ref GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+}
+
+impl LazyServiceGuard {
+ /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+ /// process.
+ pub fn new() -> Self {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count += 1;
+ if *count == 1 {
+ // It's important that we make this call with the mutex held, to make sure
+ // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+ // sequenced. (That also means we can't just use an AtomicU64.)
+ force_lazy_services_persist(true);
+ }
+ Self { _private: () }
+ }
+}
+
+impl Drop for LazyServiceGuard {
+ fn drop(&mut self) {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count -= 1;
+ if *count == 0 {
+ force_lazy_services_persist(false);
+ }
+ }
+}
+
+impl Clone for LazyServiceGuard {
+ fn clone(&self) -> Self {
+ Self::new()
+ }
+}
+
+impl Default for LazyServiceGuard {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/binder_common/lib.rs b/binder_common/lib.rs
new file mode 100644
index 0000000..055688a
--- /dev/null
+++ b/binder_common/lib.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.
+ */
+
+//! Common items useful for binder clients and/or servers.
+
+pub mod lazy_service;
+
+use binder::public_api::{ExceptionCode, Status};
+use std::ffi::CString;
+
+/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
+pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+ match exception {
+ ExceptionCode::SERVICE_SPECIFIC => new_binder_service_specific_error(-1, message),
+ _ => Status::new_exception(exception, to_cstring(message).as_deref()),
+ }
+}
+
+/// Constructs a Binder `Status` representing a service-specific exception with the given code and
+/// message.
+pub fn new_binder_service_specific_error<T: AsRef<str>>(code: i32, message: T) -> Status {
+ Status::new_service_specific_error(code, to_cstring(message).as_deref())
+}
+
+fn to_cstring<T: AsRef<str>>(message: T) -> Option<CString> {
+ CString::new(message.as_ref()).ok()
+}
diff --git a/compos/Android.bp b/compos/Android.bp
index 2af19c8..b1e5f89 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -6,6 +6,7 @@
name: "pvm_exec",
srcs: ["src/pvm_exec.rs"],
rustlibs: [
+ "android.system.composd-rust",
"compos_aidl_interface-rust",
"libandroid_logger",
"libanyhow",
@@ -34,17 +35,21 @@
rustlibs: [
"android.hardware.security.keymint-V1-rust",
"android.system.keystore2-V1-rust",
+ "android.system.virtualmachineservice-rust",
"authfs_aidl_interface-rust",
"compos_aidl_interface-rust",
"libandroid_logger",
"libanyhow",
+ "libbinder_common",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"libclap",
"libcompos_common",
+ "libenv_logger",
"liblibc",
"liblog_rust",
"libminijail_rust",
+ "libnix",
"libring",
"libscopeguard",
],
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 7904130..29c453b 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -43,7 +43,22 @@
* @param fd_annotation Additional file descriptor information of the execution
* @return a CompilationResult
*/
- CompilationResult compile(in String[] args, in FdAnnotation fd_annotation);
+ CompilationResult compile_cmd(in String[] args, in FdAnnotation fd_annotation);
+
+ /**
+ * Runs dexopt compilation encoded in the marshaled dexopt arguments.
+ *
+ * To keep ART indepdendantly updatable, the compilation arguments are not stabilized. As a
+ * result, the arguments are marshaled into byte array. Upon received, the service asks ART to
+ * return relevant information (since ART is able to unmarshal its own encoding), in order to
+ * set up the execution context (mainly file descriptors for compiler input and output) then
+ * invokes the compiler.
+ *
+ * @param marshaledArguments The marshaled dexopt arguments.
+ * @param fd_annotation Additional file descriptor information of the execution.
+ * @return exit code
+ */
+ byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
/**
* Generate a new public/private key pair suitable for signing CompOs output files.
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 9be60d0..9c71942 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -5,7 +5,10 @@
},
"task": {
"type": "executable",
- "command": "/apex/com.android.compos/bin/compsvc"
+ "command": "/apex/com.android.compos/bin/compsvc",
+ "args": [
+ "--log_to_stderr"
+ ]
},
"apexes": [
{
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
new file mode 100644
index 0000000..54a0aac
--- /dev/null
+++ b/compos/apk/assets/vm_test_config.json
@@ -0,0 +1,18 @@
+{
+ "version": 1,
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "executable",
+ "command": "/apex/com.android.compos/bin/compsvc"
+ },
+ "apexes": [
+ {
+ "name": "com.android.art"
+ },
+ {
+ "name": "com.android.compos"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 03cc331..5f14005 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -34,8 +34,9 @@
FromIBinder,
};
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
-use log::warn;
+use log::{info, warn};
use std::fs::File;
+use std::io::{BufRead, BufReader};
use std::os::raw;
use std::os::unix::io::IntoRawFd;
use std::path::Path;
@@ -45,16 +46,21 @@
/// This owns an instance of the CompOS VM.
pub struct VmInstance {
- #[allow(dead_code)] // Keeps the vm alive even if we don`t touch it
+ #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
vm: Strong<dyn IVirtualMachine>,
cid: i32,
}
impl VmInstance {
+ /// Return a new connection to the Virtualization Service binder interface. This will start the
+ /// service if necessary.
+ pub fn connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>> {
+ wait_for_interface::<dyn IVirtualizationService>("android.system.virtualizationservice")
+ .context("Failed to find VirtualizationService")
+ }
+
/// Start a new CompOS VM instance using the specified instance image file.
- pub fn start(instance_image: &Path) -> Result<VmInstance> {
- let instance_image =
- File::open(instance_image).context("Failed to open instance image file")?;
+ pub fn start(instance_image: File) -> Result<VmInstance> {
let instance_fd = ParcelFileDescriptor::new(instance_image);
let apex_dir = Path::new(COMPOS_APEX_ROOT);
@@ -80,10 +86,7 @@
..Default::default()
});
- let service = wait_for_interface::<dyn IVirtualizationService>(
- "android.system.virtualizationservice",
- )
- .context("Failed to find VirtualizationService")?;
+ let service = Self::connect_to_virtualization_service()?;
let vm = service.createVm(&config, Some(&log_fd)).context("Failed to create VM")?;
let vm_state = Arc::new(VmStateMonitor::default());
@@ -103,10 +106,7 @@
vm.start()?;
- let cid = vm_state.wait_for_start()?;
-
- // TODO: Use onPayloadReady to avoid this
- thread::sleep(Duration::from_secs(3));
+ let cid = vm_state.wait_until_ready()?;
Ok(VmInstance { vm, cid })
}
@@ -114,26 +114,17 @@
/// Create and return an RPC Binder connection to the Comp OS service in the VM.
pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
let mut vsock_factory = VsockFactory::new(&*self.vm);
- let param = vsock_factory.as_void_ptr();
- let ibinder = unsafe {
- // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
- // the ownership can be safely taken by new_spibinder.
- // RpcPreconnectedClient does not take ownership of param, only passing it to
- // request_fd.
- let binder = binder_rpc_unstable_bindgen::RpcPreconnectedClient(
- Some(VsockFactory::request_fd),
- param,
- ) as *mut AIBinder;
- new_spibinder(binder)
- }
- .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
+ let ibinder = vsock_factory
+ .connect_rpc_client()
+ .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
}
/// Return the CID of the VM.
pub fn cid(&self) -> i32 {
+ // TODO: Do we actually need/use this?
self.cid
}
}
@@ -147,6 +138,21 @@
Self { vm }
}
+ fn connect_rpc_client(&mut self) -> Option<binder::SpIBinder> {
+ let param = self.as_void_ptr();
+
+ unsafe {
+ // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
+ // the ownership can be safely taken by new_spibinder.
+ // RpcPreconnectedClient does not take ownership of param, only passing it to
+ // request_fd.
+ let binder =
+ binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
+ as *mut AIBinder;
+ new_spibinder(binder)
+ }
+ }
+
fn as_void_ptr(&mut self) -> *mut raw::c_void {
self as *mut _ as *mut raw::c_void
}
@@ -168,8 +174,8 @@
// SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
// VsockFactory, with param taking the value returned by as_void_ptr (so a properly aligned
// non-null pointer to an initialized instance).
- let holder = param as *mut Self;
- holder.as_ref().unwrap().new_vsock_fd()
+ let vsock_factory = param as *mut Self;
+ vsock_factory.as_ref().unwrap().new_vsock_fd()
}
}
@@ -206,7 +212,7 @@
self.state_ready.notify_all();
}
- fn set_started(&self, cid: i32) {
+ fn set_ready(&self, cid: i32) {
let mut state = self.mutex.lock().unwrap();
if state.has_died {
return;
@@ -216,10 +222,10 @@
self.state_ready.notify_all();
}
- fn wait_for_start(&self) -> Result<i32> {
+ fn wait_until_ready(&self) -> Result<i32> {
let (state, result) = self
.state_ready
- .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(10), |state| {
+ .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(20), |state| {
state.cid.is_none() && !state.has_died
})
.unwrap();
@@ -245,16 +251,19 @@
fn onPayloadStarted(
&self,
cid: i32,
- _stream: Option<&binder::parcel::ParcelFileDescriptor>,
+ stream: Option<&ParcelFileDescriptor>,
) -> BinderResult<()> {
- self.0.set_started(cid);
- // TODO: Use the stream?
+ if let Some(pfd) = stream {
+ if let Err(e) = start_logging(pfd) {
+ warn!("Can't log vm output: {}", e);
+ };
+ }
log::info!("VM payload started, cid = {}", cid);
Ok(())
}
fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
- // TODO: Use this to trigger vsock connection
+ self.0.set_ready(cid);
log::info!("VM payload ready, cid = {}", cid);
Ok(())
}
@@ -267,3 +276,19 @@
Ok(())
}
}
+
+fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
+ let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
+ thread::spawn(move || {
+ for line in reader.lines() {
+ match line {
+ Ok(line) => info!("VM: {}", line),
+ Err(e) => {
+ warn!("Reading VM output failed: {}", e);
+ break;
+ }
+ }
+ }
+ });
+ Ok(())
+}
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 6bea62c..104b8e5 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -18,6 +18,9 @@
pub mod compos_client;
+/// Special CID indicating "any".
+pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
+
/// VSock port that the CompOS server listens on for RPC binder connections. This should be out of
/// future port range (if happens) that microdroid may reserve for system components.
pub const COMPOS_VSOCK_PORT: u32 = 6432;
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index ff53548..e168648 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -31,6 +31,7 @@
#include <openssl/mem.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
+#include <stdio.h>
#include <unistd.h>
#include <binder_rpc_unstable.hpp>
@@ -41,6 +42,7 @@
#include <mutex>
#include <string>
#include <string_view>
+#include <thread>
#include "compos_signature.pb.h"
@@ -56,6 +58,7 @@
using aidl::com::android::compos::ICompOsService;
using android::base::ErrnoError;
using android::base::Error;
+using android::base::Fdopen;
using android::base::Result;
using android::base::unique_fd;
using compos::proto::Signature;
@@ -94,22 +97,45 @@
}
namespace {
+
+void copyToLog(unique_fd&& fd) {
+ FILE* source = Fdopen(std::move(fd), "r");
+ size_t size = 0;
+ char* line = nullptr;
+
+ LOG(INFO) << "Started logging VM output";
+
+ for (;;) {
+ ssize_t len = getline(&line, &size, source);
+ if (len < 0) {
+ LOG(INFO) << "VM logging ended: " << ErrnoError().str();
+ break;
+ }
+ LOG(DEBUG) << "VM: " << std::string_view(line, len);
+ }
+ free(line);
+}
+
class Callback : public BnVirtualMachineCallback {
public:
- ::ndk::ScopedAStatus onPayloadStarted(
- int32_t in_cid, const ::ndk::ScopedFileDescriptor& /*in_stream*/) override {
- // TODO: Consider copying stdout somewhere useful?
+ ::ndk::ScopedAStatus onPayloadStarted(int32_t in_cid,
+ const ::ndk::ScopedFileDescriptor& stream) override {
LOG(INFO) << "Payload started! cid = " << in_cid;
- {
- std::unique_lock lock(mMutex);
- mStarted = true;
- }
- mCv.notify_all();
+
+ unique_fd stream_fd(dup(stream.get()));
+ std::thread logger([fd = std::move(stream_fd)]() mutable { copyToLog(std::move(fd)); });
+ logger.detach();
+
return ScopedAStatus::ok();
}
::ndk::ScopedAStatus onPayloadReady(int32_t in_cid) override {
LOG(INFO) << "Payload is ready! cid = " << in_cid;
+ {
+ std::unique_lock lock(mMutex);
+ mReady = true;
+ }
+ mCv.notify_all();
return ScopedAStatus::ok();
}
@@ -128,16 +154,16 @@
return ScopedAStatus::ok();
}
- bool waitForStarted() {
+ bool waitUntilReady() {
std::unique_lock lock(mMutex);
- return mCv.wait_for(lock, std::chrono::seconds(10), [this] { return mStarted || mDied; }) &&
+ return mCv.wait_for(lock, std::chrono::seconds(20), [this] { return mReady || mDied; }) &&
!mDied;
}
private:
std::mutex mMutex;
std::condition_variable mCv;
- bool mStarted;
+ bool mReady;
bool mDied;
};
@@ -237,14 +263,10 @@
}
LOG(INFO) << "Started VM";
- if (!mCallback->waitForStarted()) {
+ if (!mCallback->waitUntilReady()) {
return Error() << "VM Payload failed to start";
}
- // TODO(b/194677789): Implement a polling loop or find a more reliable
- // way to detect when the service is listening.
- sleep(3);
-
return cid;
}
@@ -255,6 +277,7 @@
std::shared_ptr<Callback> mCallback;
std::shared_ptr<IVirtualMachine> mVm;
};
+
} // namespace
static Result<std::vector<uint8_t>> extractRsaPublicKey(
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 5c968b8..9887483 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -9,11 +9,14 @@
prefer_rlib: true,
rustlibs: [
"android.system.composd-rust",
+ "android.system.virtualizationservice-rust",
"compos_aidl_interface-rust",
"libandroid_logger",
"libanyhow",
+ "libbinder_common",
"libbinder_rs",
"libcompos_common",
+ "libcomposd_native_rust",
"libnum_traits",
"liblog_rust",
],
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 90c0de0..0352001 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -5,6 +5,7 @@
aidl_interface {
name: "android.system.composd",
srcs: ["android/system/composd/*.aidl"],
+ imports: ["compos_aidl_interface"],
// TODO: Make this stable when the APEX becomes updatable.
unstable: true,
backend: {
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 9240bc6..a1bb92c 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -15,7 +15,28 @@
*/
package android.system.composd;
+import com.android.compos.CompilationResult;
+import com.android.compos.FdAnnotation;
+
interface IIsolatedCompilationService {
- /// Run "odrefresh --force-compile" in CompOS
+ /** Run "odrefresh --force-compile" in CompOS. */
void runForcedCompile();
+
+ /**
+ * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
+ * to ICompOsService#compile_cmd.
+ *
+ * This method can only be called from odrefresh. If there is no currently running instance
+ * an error is returned.
+ */
+ CompilationResult compile_cmd(in String[] args, in FdAnnotation fd_annotation);
+
+ /**
+ * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
+ * to ICompOsService#compile.
+ *
+ * This method can only be called from libcompos_client. If there is no currently running
+ * instance an error is returned.
+ */
+ byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
}
diff --git a/compos/composd/native/Android.bp b/compos/composd/native/Android.bp
new file mode 100644
index 0000000..ad0afd9
--- /dev/null
+++ b/compos/composd/native/Android.bp
@@ -0,0 +1,42 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libcomposd_native_rust",
+ crate_name: "composd_native",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "libcxx",
+ ],
+ static_libs: [
+ "libcomposd_native_cpp",
+ ],
+ shared_libs: ["libcrypto"],
+ apex_available: ["com.android.compos"],
+}
+
+cc_library_static {
+ name: "libcomposd_native_cpp",
+ srcs: ["composd_native.cpp"],
+ shared_libs: ["libcrypto"],
+ generated_headers: ["composd_native_header"],
+ generated_sources: ["composd_native_code"],
+ apex_available: ["com.android.compos"],
+}
+
+genrule {
+ name: "composd_native_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["composd_native_cxx_generated.cc"],
+}
+
+genrule {
+ name: "composd_native_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["lib.rs.h"],
+}
diff --git a/compos/composd/native/composd_native.cpp b/compos/composd/native/composd_native.cpp
new file mode 100644
index 0000000..ebed816
--- /dev/null
+++ b/compos/composd/native/composd_native.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include "composd_native.h"
+
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+
+#include <algorithm>
+#include <iterator>
+
+using rust::Slice;
+using rust::String;
+
+namespace {
+KeyResult make_error(const char* message) {
+ return KeyResult{{}, message};
+}
+} // namespace
+
+KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate) {
+ auto data = der_certificate.data();
+ bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
+ if (!x509) {
+ return make_error("Failed to parse certificate");
+ }
+ if (data != der_certificate.data() + der_certificate.size()) {
+ return make_error("Certificate has unexpected trailing data");
+ }
+
+ bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
+ if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
+ return make_error("Subject key is not RSA");
+ }
+ RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
+ if (!rsa) {
+ return make_error("Failed to extract RSA key");
+ }
+
+ uint8_t* out = nullptr;
+ int size = i2d_RSAPublicKey(rsa, &out);
+ if (size < 0 || !out) {
+ return make_error("Failed to convert to RSAPublicKey");
+ }
+ bssl::UniquePtr<uint8_t> buffer(out);
+
+ KeyResult result;
+ result.key.reserve(size);
+ std::copy(out, out + size, std::back_inserter(result.key));
+ return result;
+}
diff --git a/authfs/service/src/common.rs b/compos/composd/native/composd_native.h
similarity index 65%
rename from authfs/service/src/common.rs
rename to compos/composd/native/composd_native.h
index 00efe9e..112ef73 100644
--- a/authfs/service/src/common.rs
+++ b/compos/composd/native/composd_native.h
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-use std::ffi::CString;
+#pragma once
-use authfs_aidl_interface::binder::{ExceptionCode, Status};
+#include "lib.rs.h"
-/// Helper function to create a binder exception.
-pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
- Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
+KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate);
diff --git a/compos/composd/native/lib.rs b/compos/composd/native/lib.rs
new file mode 100644
index 0000000..ace9600
--- /dev/null
+++ b/compos/composd/native/lib.rs
@@ -0,0 +1,38 @@
+// 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.
+
+//! Bindings native helpers for composd.
+
+pub use ffi::*;
+
+#[cxx::bridge]
+mod ffi {
+ /// Contains either a key or a reason why the key could not be extracted.
+ struct KeyResult {
+ /// The extracted key. If empty, the attempt to extract the key failed.
+ key: Vec<u8>,
+ /// A description of what went wrong if the attempt failed.
+ error: String,
+ }
+
+ unsafe extern "C++" {
+ include!("composd_native.h");
+
+ // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
+ // the der_certificate reference. cxx handles the mapping of the return value.
+
+ /// Parse the supplied DER X.509 certificate and extract the subject's RsaPublicKey.
+ fn extract_rsa_public_key(der_certificate: &[u8]) -> KeyResult;
+ }
+}
diff --git a/compos/composd/src/compos_instance.rs b/compos/composd/src/compos_instance.rs
deleted file mode 100644
index e30a8b3..0000000
--- a/compos/composd/src/compos_instance.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-//! Starts and manages instances of the CompOS VM.
-
-use anyhow::{Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
-use compos_aidl_interface::binder::Strong;
-use compos_common::compos_client::VmInstance;
-use compos_common::{COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE};
-use std::fs;
-use std::path::PathBuf;
-
-#[allow(dead_code)]
-pub struct CompOsInstance {
- instance: VmInstance,
- service: Strong<dyn ICompOsService>,
-}
-
-impl CompOsInstance {
- pub fn start_current_instance() -> Result<CompOsInstance> {
- let instance_image: PathBuf =
- [COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE].iter().collect();
-
- let instance = VmInstance::start(&instance_image).context("Starting VM")?;
- let service = instance.get_service().context("Connecting to CompOS")?;
-
- let key_blob: PathBuf =
- [COMPOS_DATA_ROOT, CURRENT_DIR, PRIVATE_KEY_BLOB_FILE].iter().collect();
- let key_blob = fs::read(key_blob).context("Reading private key")?;
- service.initializeSigningKey(&key_blob).context("Loading key")?;
-
- Ok(CompOsInstance { instance, service })
- }
-
- pub fn cid(&self) -> i32 {
- self.instance.cid()
- }
-}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 33da889..60aeb39 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -18,12 +18,15 @@
//! responsible for managing the lifecycle of the CompOS VM instances, providing key management for
//! them, and orchestrating trusted compilation.
-mod compos_instance;
+mod instance_manager;
+mod instance_starter;
mod odrefresh;
mod service;
+use crate::instance_manager::InstanceManager;
use android_system_composd::binder::{register_lazy_service, ProcessState};
use anyhow::{Context, Result};
+use compos_common::compos_client::VmInstance;
use log::{error, info};
fn try_main() -> Result<()> {
@@ -33,8 +36,10 @@
ProcessState::start_thread_pool();
- let service = service::new_binder();
- register_lazy_service("android.system.composd", service.as_binder())
+ let virtualization_service = VmInstance::connect_to_virtualization_service()?;
+ let instance_manager = InstanceManager::new(virtualization_service);
+ let composd_service = service::new_binder(instance_manager);
+ register_lazy_service("android.system.composd", composd_service.as_binder())
.context("Registering service")?;
info!("Registered service, joining threadpool");
@@ -46,7 +51,7 @@
fn main() {
if let Err(e) = try_main() {
- error!("{}", e);
+ error!("{:?}", e);
std::process::exit(1)
}
}
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
new file mode 100644
index 0000000..6b36ed8
--- /dev/null
+++ b/compos/composd/src/instance_manager.rs
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+//! Manages running instances of the CompOS VM. At most one instance should be running at
+//! a time, started on demand.
+
+use crate::instance_starter::{CompOsInstance, InstanceStarter};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::binder::Strong;
+use compos_common::CURRENT_DIR;
+use std::sync::{Arc, Mutex, Weak};
+use virtualizationservice::IVirtualizationService::IVirtualizationService;
+
+pub struct InstanceManager {
+ service: Strong<dyn IVirtualizationService>,
+ state: Mutex<State>,
+}
+
+impl InstanceManager {
+ pub fn new(service: Strong<dyn IVirtualizationService>) -> Self {
+ Self { service, state: Default::default() }
+ }
+
+ pub fn get_running_service(&self) -> Result<Strong<dyn ICompOsService>> {
+ let mut state = self.state.lock().unwrap();
+ let instance = state.get_running_instance().context("No running instance")?;
+ Ok(instance.get_service())
+ }
+
+ pub fn start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+ let mut state = self.state.lock().unwrap();
+ state.mark_starting()?;
+ // Don't hold the lock while we start the instance to avoid blocking other callers.
+ drop(state);
+
+ let instance = self.try_start_current_instance();
+
+ let mut state = self.state.lock().unwrap();
+ if let Ok(ref instance) = instance {
+ state.mark_started(instance)?;
+ } else {
+ state.mark_stopped();
+ }
+ instance
+ }
+
+ fn try_start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+ let instance_starter = InstanceStarter::new(CURRENT_DIR);
+ let compos_instance = instance_starter.create_or_start_instance(&*self.service)?;
+
+ Ok(Arc::new(compos_instance))
+ }
+}
+
+// Ensures we only run one instance at a time.
+// Valid states:
+// Starting: is_starting is true, running_instance is None.
+// Started: is_starting is false, running_instance is Some(x) and there is a strong ref to x.
+// Stopped: is_starting is false and running_instance is None or a weak ref to a dropped instance.
+// The panic calls here should never happen, unless the code above in InstanceManager is buggy.
+// In particular nothing the client does should be able to trigger them.
+#[derive(Default)]
+struct State {
+ running_instance: Option<Weak<CompOsInstance>>,
+ is_starting: bool,
+}
+
+impl State {
+ // Move to Starting iff we are Stopped.
+ fn mark_starting(&mut self) -> Result<()> {
+ if self.is_starting {
+ bail!("An instance is already starting");
+ }
+ if let Some(weak) = &self.running_instance {
+ if weak.strong_count() != 0 {
+ bail!("An instance is already running");
+ }
+ }
+ self.running_instance = None;
+ self.is_starting = true;
+ Ok(())
+ }
+
+ // Move from Starting to Stopped.
+ fn mark_stopped(&mut self) {
+ if !self.is_starting || self.running_instance.is_some() {
+ panic!("Tried to mark stopped when not starting");
+ }
+ self.is_starting = false;
+ }
+
+ // Move from Starting to Started.
+ fn mark_started(&mut self, instance: &Arc<CompOsInstance>) -> Result<()> {
+ if !self.is_starting {
+ panic!("Tried to mark started when not starting")
+ }
+ if self.running_instance.is_some() {
+ panic!("Attempted to mark started when already started");
+ }
+ self.is_starting = false;
+ self.running_instance = Some(Arc::downgrade(instance));
+ Ok(())
+ }
+
+ // Return the running instance if we are in the Started state.
+ fn get_running_instance(&mut self) -> Option<Arc<CompOsInstance>> {
+ if self.is_starting {
+ return None;
+ }
+ let instance = self.running_instance.as_ref()?.upgrade();
+ if instance.is_none() {
+ // No point keeping an orphaned weak reference
+ self.running_instance = None;
+ }
+ instance
+ }
+}
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
new file mode 100644
index 0000000..1751d35
--- /dev/null
+++ b/compos/composd/src/instance_starter.rs
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+//! Responsible for validating and starting an existing instance of the CompOS VM, or creating and
+//! starting a new instance if necessary.
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
+};
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
+use compos_common::compos_client::VmInstance;
+use compos_common::{
+ COMPOS_DATA_ROOT, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
+};
+use log::{info, warn};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+pub struct CompOsInstance {
+ #[allow(dead_code)] // Keeps VirtualizationService & the VM alive
+ vm_instance: VmInstance,
+ service: Strong<dyn ICompOsService>,
+}
+
+impl CompOsInstance {
+ pub fn get_service(&self) -> Strong<dyn ICompOsService> {
+ self.service.clone()
+ }
+}
+
+pub struct InstanceStarter {
+ instance_name: String,
+ instance_root: PathBuf,
+ instance_image: PathBuf,
+ key_blob: PathBuf,
+ public_key: PathBuf,
+}
+
+impl InstanceStarter {
+ pub fn new(instance_name: &str) -> Self {
+ let instance_root = Path::new(COMPOS_DATA_ROOT).join(instance_name);
+ let instant_root_path = instance_root.as_path();
+ let instance_image = instant_root_path.join(INSTANCE_IMAGE_FILE);
+ let key_blob = instant_root_path.join(PRIVATE_KEY_BLOB_FILE);
+ let public_key = instant_root_path.join(PUBLIC_KEY_FILE);
+ Self {
+ instance_name: instance_name.to_owned(),
+ instance_root,
+ instance_image,
+ key_blob,
+ public_key,
+ }
+ }
+
+ pub fn create_or_start_instance(
+ &self,
+ service: &dyn IVirtualizationService,
+ ) -> Result<CompOsInstance> {
+ let compos_instance = self.start_existing_instance();
+ match compos_instance {
+ Ok(_) => return compos_instance,
+ Err(e) => warn!("Failed to start: {}", e),
+ }
+
+ self.start_new_instance(service)
+ }
+
+ fn start_existing_instance(&self) -> Result<CompOsInstance> {
+ // No point even trying if the files we need aren't there.
+ self.check_files_exist()?;
+
+ info!("Starting {} CompOs instance", self.instance_name);
+
+ let key_blob = fs::read(&self.key_blob).context("Reading private key blob")?;
+ let public_key = fs::read(&self.public_key).context("Reading public key")?;
+
+ let compos_instance = self.start_vm()?;
+ let service = &compos_instance.service;
+
+ if !service.verifySigningKey(&key_blob, &public_key).context("Verifying key pair")? {
+ bail!("Key pair invalid");
+ }
+
+ // If we get this far then the instance image is valid in the current context (e.g. the
+ // current set of APEXes) and the key blob can be successfully decrypted by the VM. So the
+ // files have not been tampered with and we're good to go.
+
+ service.initializeSigningKey(&key_blob).context("Loading signing key")?;
+
+ Ok(compos_instance)
+ }
+
+ fn start_new_instance(
+ &self,
+ virtualization_service: &dyn IVirtualizationService,
+ ) -> Result<CompOsInstance> {
+ info!("Creating {} CompOs instance", self.instance_name);
+
+ // Ignore failure here - the directory may already exist.
+ let _ = fs::create_dir(&self.instance_root);
+
+ self.create_instance_image(virtualization_service)?;
+
+ let compos_instance = self.start_vm()?;
+ let service = &compos_instance.service;
+
+ let key_data = service.generateSigningKey().context("Generating signing key")?;
+ fs::write(&self.key_blob, &key_data.keyBlob).context("Writing key blob")?;
+
+ let key_result = composd_native::extract_rsa_public_key(&key_data.certificate);
+ let rsa_public_key = key_result.key;
+ if rsa_public_key.is_empty() {
+ bail!("Failed to extract public key from certificate: {}", key_result.error);
+ }
+ fs::write(&self.public_key, &rsa_public_key).context("Writing public key")?;
+
+ // We don't need to verify the key, since we just generated it and have it in memory.
+
+ service.initializeSigningKey(&key_data.keyBlob).context("Loading signing key")?;
+
+ Ok(compos_instance)
+ }
+
+ fn start_vm(&self) -> Result<CompOsInstance> {
+ let instance_image = fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&self.instance_image)
+ .context("Failed to open instance image")?;
+ let vm_instance = VmInstance::start(instance_image).context("Starting VM")?;
+ let service = vm_instance.get_service().context("Connecting to CompOS")?;
+ Ok(CompOsInstance { vm_instance, service })
+ }
+
+ fn create_instance_image(
+ &self,
+ virtualization_service: &dyn IVirtualizationService,
+ ) -> Result<()> {
+ let instance_image = fs::OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .open(&self.instance_image)
+ .context("Creating instance image file")?;
+ let instance_image = ParcelFileDescriptor::new(instance_image);
+ // TODO: Where does this number come from?
+ let size = 10 * 1024 * 1024;
+ virtualization_service
+ .initializeWritablePartition(&instance_image, size, PartitionType::ANDROID_VM_INSTANCE)
+ .context("Writing instance image file")?;
+ Ok(())
+ }
+
+ fn check_files_exist(&self) -> Result<()> {
+ if !self.instance_root.is_dir() {
+ bail!("Directory {} not found", self.instance_root.display())
+ };
+ Self::check_file_exists(&self.instance_image)?;
+ Self::check_file_exists(&self.key_blob)?;
+ Self::check_file_exists(&self.public_key)?;
+ Ok(())
+ }
+
+ fn check_file_exists(file: &Path) -> Result<()> {
+ if !file.is_file() {
+ bail!("File {} not found", file.display())
+ };
+ Ok(())
+ }
+}
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index c0042f0..54da231 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -17,6 +17,7 @@
//! Handle the details of executing odrefresh to generate compiled artifacts.
use anyhow::{bail, Context, Result};
+use compos_common::VMADDR_CID_ANY;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::process::Command;
@@ -36,10 +37,10 @@
CleanupFailed = EX_MAX + 4,
}
-pub fn run_forced_compile(cid: i32) -> Result<ExitCode> {
+pub fn run_forced_compile() -> Result<ExitCode> {
// We don`t need to capture stdout/stderr - odrefresh writes to the log
let mut odrefresh = Command::new(ODREFRESH_BIN)
- .arg(format!("--use-compilation-os={}", cid))
+ .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY))
.arg("--force-compile")
.spawn()
.context("Running odrefresh")?;
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 7fc9ab0..be9c30c 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -17,20 +17,25 @@
//! Implementation of IIsolatedCompilationService, called from system server when compilation is
//! desired.
-use crate::compos_instance::CompOsInstance;
+use crate::instance_manager::InstanceManager;
use crate::odrefresh;
use android_system_composd::aidl::android::system::composd::IIsolatedCompilationService::{
BnIsolatedCompilationService, IIsolatedCompilationService,
};
-use android_system_composd::binder::{self, BinderFeatures, Interface, Status, Strong};
+use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
use anyhow::{bail, Context, Result};
+use binder_common::new_binder_service_specific_error;
+use compos_aidl_interface::aidl::com::android::compos::{
+ CompilationResult::CompilationResult, FdAnnotation::FdAnnotation,
+};
use log::{error, info};
-use std::ffi::CString;
-pub struct IsolatedCompilationService {}
+pub struct IsolatedCompilationService {
+ instance_manager: InstanceManager,
+}
-pub fn new_binder() -> Strong<dyn IIsolatedCompilationService> {
- let service = IsolatedCompilationService {};
+pub fn new_binder(instance_manager: InstanceManager) -> Strong<dyn IIsolatedCompilationService> {
+ let service = IsolatedCompilationService { instance_manager };
BnIsolatedCompilationService::new_binder(service, BinderFeatures::default())
}
@@ -38,14 +43,29 @@
impl IIsolatedCompilationService for IsolatedCompilationService {
fn runForcedCompile(&self) -> binder::Result<()> {
+ // TODO - check caller is system or shell/root?
to_binder_result(self.do_run_forced_compile())
}
+
+ fn compile_cmd(
+ &self,
+ args: &[String],
+ fd_annotation: &FdAnnotation,
+ ) -> binder::Result<CompilationResult> {
+ // TODO - check caller is odrefresh
+ to_binder_result(self.do_compile(args, fd_annotation))
+ }
+
+ fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> binder::Result<i8> {
+ Err(new_binder_service_specific_error(-1, "Not yet implemented"))
+ }
}
fn to_binder_result<T>(result: Result<T>) -> binder::Result<T> {
result.map_err(|e| {
- error!("Returning binder error: {:#}", e);
- Status::new_service_specific_error(-1, CString::new(format!("{:#}", e)).ok().as_deref())
+ let message = format!("{:?}", e);
+ error!("Returning binder error: {}", &message);
+ new_binder_service_specific_error(-1, message)
})
}
@@ -53,16 +73,26 @@
fn do_run_forced_compile(&self) -> Result<()> {
info!("runForcedCompile");
- // TODO: Create instance if need be, handle instance failure, prevent
- // multiple instances running
- let comp_os = CompOsInstance::start_current_instance().context("Starting CompOS")?;
+ let comp_os = self.instance_manager.start_current_instance().context("Starting CompOS")?;
- let exit_code = odrefresh::run_forced_compile(comp_os.cid())?;
+ let exit_code = odrefresh::run_forced_compile()?;
if exit_code != odrefresh::ExitCode::CompilationSuccess {
bail!("Unexpected odrefresh result: {:?}", exit_code);
}
+ // The instance is needed until odrefresh is finished
+ drop(comp_os);
+
Ok(())
}
+
+ fn do_compile(
+ &self,
+ args: &[String],
+ fd_annotation: &FdAnnotation,
+ ) -> Result<CompilationResult> {
+ let compos = self.instance_manager.get_running_service()?;
+ compos.compile_cmd(args, fd_annotation).context("Compiling")
+ }
}
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index fec82a6..1499d4b 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -52,7 +52,7 @@
/// Runs the compiler with given flags with file descriptors described in `fd_annotation` retrieved
/// via `authfs_service`. Returns exit code of the compiler process.
-pub fn compile(
+pub fn compile_cmd(
compiler_path: &Path,
compiler_args: &[String],
authfs_service: Strong<dyn IAuthFsService>,
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 55d9d64..08f3521 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,13 +19,13 @@
//! actual compiler.
use anyhow::Result;
+use binder_common::new_binder_exception;
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::compilation::{compile_cmd, CompilerOutput};
use crate::compos_key_service::CompOsKeyService;
use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
@@ -36,7 +36,7 @@
ICompOsService::{BnCompOsService, ICompOsService},
};
use compos_aidl_interface::binder::{
- BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong,
+ BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
};
const AUTHFS_SERVICE_NAME: &str = "authfs_service";
@@ -85,14 +85,14 @@
}
}
- fn compile(
+ fn compile_cmd(
&self,
args: &[String],
fd_annotation: &FdAnnotation,
) -> BinderResult<CompilationResult> {
let authfs_service = get_authfs_service()?;
let output =
- compile(&self.dex2oat_path, args, authfs_service, fd_annotation).map_err(|e| {
+ compile_cmd(&self.dex2oat_path, args, authfs_service, fd_annotation).map_err(|e| {
new_binder_exception(
ExceptionCode::SERVICE_SPECIFIC,
format!("Compilation failed: {}", e),
@@ -124,6 +124,10 @@
}
}
+ fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> BinderResult<i8> {
+ Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not yet implemented"))
+ }
+
fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
self.key_service
.do_generate()
@@ -154,7 +158,3 @@
fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
}
-
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
- Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 9855b53..d0c920a 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -22,24 +22,61 @@
mod fsverity;
mod signer;
-use anyhow::{bail, Result};
-use binder::unstable_api::AsNative;
+use android_system_virtualmachineservice::{
+ aidl::android::system::virtualmachineservice::IVirtualMachineService::{
+ IVirtualMachineService, VM_BINDER_SERVICE_PORT,
+ },
+ binder::Strong,
+};
+use anyhow::{anyhow, bail, Context, Result};
+use binder::{
+ unstable_api::{new_spibinder, AIBinder, AsNative},
+ FromIBinder,
+};
use compos_common::COMPOS_VSOCK_PORT;
-use log::debug;
+use log::{debug, error};
+use nix::ioctl_read_bad;
+use std::fs::OpenOptions;
+use std::os::raw;
+use std::os::unix::io::AsRawFd;
-fn main() -> Result<()> {
- android_logger::init_once(
- android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
- );
+/// The CID representing the host VM
+const VMADDR_CID_HOST: u32 = 2;
+
+fn main() {
+ if let Err(e) = try_main() {
+ error!("failed with {:?}", e);
+ std::process::exit(1);
+ }
+}
+
+fn try_main() -> Result<()> {
+ let args = clap::App::new("compsvc")
+ .arg(clap::Arg::with_name("log_to_stderr").long("log_to_stderr"))
+ .get_matches();
+ if args.is_present("log_to_stderr") {
+ env_logger::builder().filter_level(log::LevelFilter::Debug).init();
+ } else {
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
+ );
+ }
let mut service = compsvc::new_binder()?.as_binder();
debug!("compsvc is starting as a rpc service.");
+
+ let mut ready_notifier = ReadyNotifier::new()?;
+
// SAFETY: Service ownership is transferring to the server and won't be valid afterward.
// Plus the binder objects are threadsafe.
+ // RunRpcServerCallback does not retain a reference to ready_callback, and only ever
+ // calls it with the param we provide during the lifetime of ready_notifier.
let retval = unsafe {
- binder_rpc_unstable_bindgen::RunRpcServer(
+ binder_rpc_unstable_bindgen::RunRpcServerCallback(
service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
COMPOS_VSOCK_PORT,
+ Some(ReadyNotifier::ready_callback),
+ ready_notifier.as_void_ptr(),
)
};
if retval {
@@ -49,3 +86,69 @@
bail!("Premature termination of RPC server");
}
}
+
+struct ReadyNotifier {
+ vm_service: Strong<dyn IVirtualMachineService>,
+ local_cid: u32,
+}
+
+impl ReadyNotifier {
+ fn new() -> Result<Self> {
+ Ok(Self { vm_service: Self::get_vm_service()?, local_cid: Self::get_local_cid()? })
+ }
+
+ fn notify(&self) {
+ if let Err(e) = self.vm_service.notifyPayloadReady(self.local_cid as i32) {
+ error!("Unable to notify ready: {}", e);
+ }
+ }
+
+ fn as_void_ptr(&mut self) -> *mut raw::c_void {
+ self as *mut _ as *mut raw::c_void
+ }
+
+ unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
+ // SAFETY: This is only ever called by RunRpcServerCallback, within the lifetime of the
+ // ReadyNotifier, with param taking the value returned by as_void_ptr (so a properly aligned
+ // non-null pointer to an initialized instance).
+ let ready_notifier = param as *mut Self;
+ ready_notifier.as_ref().unwrap().notify()
+ }
+
+ fn get_vm_service() -> Result<Strong<dyn IVirtualMachineService>> {
+ // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership
+ // can be safely taken by new_spibinder.
+ let ibinder = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::RpcClient(
+ VMADDR_CID_HOST,
+ VM_BINDER_SERVICE_PORT as u32,
+ ) as *mut AIBinder)
+ }
+ .ok_or_else(|| anyhow!("Failed to connect to IVirtualMachineService"))?;
+
+ FromIBinder::try_from(ibinder).context("Connecting to IVirtualMachineService")
+ }
+
+ // TODO(b/199259751): remove this after VS can check the peer addresses of binder clients
+ fn get_local_cid() -> Result<u32> {
+ let f = OpenOptions::new()
+ .read(true)
+ .write(false)
+ .open("/dev/vsock")
+ .context("Failed to open /dev/vsock")?;
+ let mut cid = 0;
+ // SAFETY: the kernel only modifies the given u32 integer.
+ unsafe { vm_sockets_get_local_cid(f.as_raw_fd(), &mut cid) }
+ .context("Failed to get local CID")?;
+ Ok(cid)
+ }
+}
+
+// TODO(b/199259751): remove this after VS can check the peer addresses of binder clients
+const IOCTL_VM_SOCKETS_GET_LOCAL_CID: usize = 0x7b9;
+ioctl_read_bad!(
+ /// Gets local cid from /dev/vsock
+ vm_sockets_get_local_cid,
+ IOCTL_VM_SOCKETS_GET_LOCAL_CID,
+ u32
+);
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
index b6fc729..fd5ffaf 100644
--- a/compos/src/pvm_exec.rs
+++ b/compos/src/pvm_exec.rs
@@ -32,19 +32,31 @@
use clap::{value_t, App, Arg};
use log::{debug, error, warn};
use minijail::Minijail;
-use nix::fcntl::{fcntl, FcntlArg::F_GETFD};
-use std::os::unix::io::RawFd;
+use nix::fcntl::{fcntl, FcntlArg::F_GETFD, OFlag};
+use nix::unistd::pipe2;
+use std::fs::File;
+use std::io::Read;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use std::process::exit;
+use android_system_composd::{
+ aidl::android::system::composd::IIsolatedCompilationService::IIsolatedCompilationService,
+ binder::wait_for_interface,
+};
use compos_aidl_interface::aidl::com::android::compos::{
FdAnnotation::FdAnnotation, ICompOsService::ICompOsService,
};
use compos_aidl_interface::binder::Strong;
-use compos_common::COMPOS_VSOCK_PORT;
+use compos_common::{COMPOS_VSOCK_PORT, VMADDR_CID_ANY};
const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
+fn get_composd() -> Result<Strong<dyn IIsolatedCompilationService>> {
+ wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+ .context("Failed to find IIsolatedCompilationService")
+}
+
fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompOsService>> {
// SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
// safely taken by new_spibinder.
@@ -60,7 +72,11 @@
}
}
-fn spawn_fd_server(fd_annotation: &FdAnnotation, debuggable: bool) -> Result<Minijail> {
+fn spawn_fd_server(
+ fd_annotation: &FdAnnotation,
+ ready_file: File,
+ debuggable: bool,
+) -> Result<Minijail> {
let mut inheritable_fds = if debuggable {
vec![1, 2] // inherit/redirect stdout/stderr for debugging
} else {
@@ -78,6 +94,10 @@
args.push(fd.to_string());
inheritable_fds.push(*fd);
}
+ let ready_fd = ready_file.as_raw_fd();
+ args.push("--ready-fd".to_string());
+ args.push(ready_fd.to_string());
+ inheritable_fds.push(ready_fd);
let jail = Minijail::new()?;
let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
@@ -144,18 +164,31 @@
Ok(Config { args, fd_annotation: FdAnnotation { input_fds, output_fds }, cid, debuggable })
}
-fn main() -> Result<()> {
- let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
- let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
- android_logger::init_once(
- android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
- );
+fn create_pipe() -> Result<(File, File)> {
+ let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
+ // SAFETY: We are the sole owners of these fds as they were just created.
+ let read_fd = unsafe { File::from_raw_fd(raw_read) };
+ let write_fd = unsafe { File::from_raw_fd(raw_write) };
+ Ok((read_fd, write_fd))
+}
+fn wait_for_fd_server_ready(mut ready_fd: File) -> Result<()> {
+ let mut buffer = [0];
+ // When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is also
+ // closed. Either way this read will return 0 bytes at that point, and there's no point waiting
+ // any longer.
+ let _ = ready_fd.read(&mut buffer).context("Waiting for fd_server to be ready")?;
+ debug!("fd_server is ready");
+ Ok(())
+}
+
+fn try_main() -> Result<()> {
// 1. Parse the command line arguments for collect execution data.
let Config { args, fd_annotation, cid, debuggable } = parse_args()?;
// 2. Spawn and configure a fd_server to serve remote read/write requests.
- let fd_server_jail = spawn_fd_server(&fd_annotation, debuggable)?;
+ let (ready_read_fd, ready_write_fd) = create_pipe()?;
+ let fd_server_jail = spawn_fd_server(&fd_annotation, ready_write_fd, debuggable)?;
let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| {
if let Err(e) = fd_server_jail.kill() {
if !matches!(e, minijail::Error::Killed(_)) {
@@ -165,8 +198,18 @@
});
// 3. Send the command line args to the remote to execute.
- let service = get_rpc_binder(cid)?;
- let result = service.compile(&args, &fd_annotation).context("Binder call failed")?;
+ let result = if cid == VMADDR_CID_ANY {
+ // Sentinel value that indicates we should use composd
+ let composd = get_composd()?;
+ wait_for_fd_server_ready(ready_read_fd)?;
+ composd.compile_cmd(&args, &fd_annotation)
+ } else {
+ // Call directly into the VM
+ let compos_vm = get_rpc_binder(cid)?;
+ wait_for_fd_server_ready(ready_read_fd)?;
+ compos_vm.compile_cmd(&args, &fd_annotation)
+ };
+ let result = result.context("Binder call failed")?;
// TODO: store/use the signature
debug!(
@@ -179,9 +222,24 @@
// Be explicit about the lifetime, which should last at least until the task is finished.
drop(fd_server_lifetime);
- if result.exitCode > 0 {
+ if result.exitCode != 0 {
error!("remote execution failed with exit code {}", result.exitCode);
exit(result.exitCode as i32);
}
Ok(())
}
+
+fn main() {
+ let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
+ let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
+ );
+
+ // Make sure we log and indicate failure if we were unable to run the command and get its exit
+ // code.
+ if let Err(e) = try_main() {
+ error!("{:?}", e);
+ std::process::exit(-1)
+ }
+}
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 64fd969..d9f7065 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -158,8 +158,9 @@
getBuild(),
apkName,
packageName,
- "assets/vm_config.json",
- /* debug */ false);
+ "assets/vm_test_config.json",
+ /* debug */ false,
+ /* use default memoryMib */ 0);
adbConnectToMicrodroid(getDevice(), mCid);
}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 8409f44..40f95c3 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -149,8 +149,9 @@
getBuild(),
apkName,
packageName,
- "assets/vm_config.json",
- /* debug */ false);
+ "assets/vm_test_config.json",
+ /* debug */ false,
+ /* Use default memory */ 0);
adbConnectToMicrodroid(getDevice(), mCid);
}
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 8439b97..0cc6473 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -87,8 +87,9 @@
let blob = read_small_file(blob).context("Failed to read key blob")?;
let public_key = read_small_file(public_key).context("Failed to read public key")?;
+ let instance_image = File::open(instance_image).context("Failed to open instance image")?;
- let vm_instance = VmInstance::start(&instance_image)?;
+ let vm_instance = VmInstance::start(instance_image)?;
let service = vm_instance.get_service()?;
let result = service.verifySigningKey(&blob, &public_key).context("Verifying signing key")?;
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index e4334cb..b61ae18 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -102,7 +102,6 @@
common: {
deps: [
// non-updatable & mandatory apexes
- "com.android.i18n",
"com.android.runtime",
"microdroid_plat_sepolicy.cil",
@@ -173,7 +172,6 @@
deps: [
"android.hardware.security.keymint-service.microdroid",
"microdroid_fstab",
- "microdroid_precompiled_sepolicy",
"microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
"microdroid_vendor_manifest",
"microdroid_vendor_compatibility_matrix",
@@ -184,6 +182,7 @@
"microdroid_vendor_sepolicy.cil",
"microdroid_plat_pub_versioned.cil",
"microdroid_plat_sepolicy_vers.txt",
+ "microdroid_precompiled_sepolicy",
],
},
},
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 9c1792d..dc72c95 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -90,7 +90,7 @@
fn main() {
if let Err(e) = try_main() {
- error!("failed with {}", e);
+ error!("failed with {:?}", e);
std::process::exit(1);
}
}
@@ -246,7 +246,8 @@
info!("notifying payload started");
service.notifyPayloadStarted(local_cid as i32)?;
- if let Some(code) = child.wait()?.code() {
+ let exit_status = child.wait()?;
+ if let Some(code) = exit_status.code() {
info!("notifying payload finished");
service.notifyPayloadFinished(local_cid as i32, code)?;
@@ -256,7 +257,7 @@
error!("task exited with exit code: {}", code);
}
} else {
- error!("task terminated by signal");
+ error!("task terminated: {}", exit_status);
}
Ok(())
}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 4c8f5eb..24a955b 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -167,7 +167,8 @@
String apkName,
String packageName,
String configPath,
- boolean debug)
+ boolean debug,
+ int memoryMib)
throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(androidDevice);
@@ -198,6 +199,7 @@
"run-app",
"--daemonize",
"--log " + logPath,
+ "--mem " + memoryMib,
debugFlag,
apkPath,
outApkIdsigPath,
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index a7b855a..6548428 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -18,7 +18,10 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
@@ -31,6 +34,22 @@
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
+ private static final int MIN_MEM_ARM64 = 125;
+ private static final int MIN_MEM_X86_64 = 270;
+
+ private int minMemorySize() throws DeviceNotAvailableException {
+ CommandRunner android = new CommandRunner(getDevice());
+ String abi = android.run("getprop", "ro.product.cpu.abi");
+ assertTrue(abi != null && !abi.isEmpty());
+ if (abi.startsWith("arm64")) {
+ return MIN_MEM_ARM64;
+ } else if (abi.startsWith("x86_64")) {
+ return MIN_MEM_X86_64;
+ }
+ fail("Unsupported ABI: " + abi);
+ return 0;
+ }
+
@Test
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
@@ -41,7 +60,8 @@
APK_NAME,
PACKAGE_NAME,
configPath,
- /* debug */ false);
+ /* debug */ false,
+ minMemorySize());
adbConnectToMicrodroid(getDevice(), cid);
// Wait until logd-init starts. The service is one of the last services that are started in
@@ -89,7 +109,14 @@
final String configPath = "assets/vm_config.json"; // path inside the APK
final boolean debug = true;
final String cid =
- startMicrodroid(getDevice(), getBuild(), APK_NAME, PACKAGE_NAME, configPath, debug);
+ startMicrodroid(
+ getDevice(),
+ getBuild(),
+ APK_NAME,
+ PACKAGE_NAME,
+ configPath,
+ debug,
+ minMemorySize());
adbConnectToMicrodroid(getDevice(), cid);
assertThat(runOnMicrodroid("getenforce"), is("Permissive"));
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 54b32ec..443436d 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -25,6 +25,7 @@
"android.os.permissions_aidl-rust",
"libandroid_logger",
"libanyhow",
+ "libbinder_common",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"libcommand_fds",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 571cc5d..30a4b03 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -39,7 +39,10 @@
backend: {
rust: {
enabled: true,
- apex_available: ["com.android.virt"],
+ apex_available: [
+ "com.android.virt",
+ "com.android.compos",
+ ],
},
},
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 6679da6..a072060 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -19,12 +19,11 @@
use crate::payload::add_microdroid_images;
use crate::{Cid, FIRST_GUEST_CID, SYSPROP_LAST_CID};
+use ::binder::unstable_api::AsNative;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::{
- BnVirtualMachine, IVirtualMachine,
-};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
DiskImage::DiskImage,
+ IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
PartitionType::PartitionType,
@@ -35,21 +34,24 @@
VirtualMachineState::VirtualMachineState,
};
use android_system_virtualizationservice::binder::{
- self, force_lazy_services_persist, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
+ self, 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,
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
+ IVirtualMachineService::{
+ BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
+ VM_STREAM_SERVICE_PORT,
+ },
};
use anyhow::{anyhow, bail, Context, Result};
-use ::binder::unstable_api::AsNative;
+use binder_common::{lazy_service::LazyServiceGuard, new_binder_exception};
use disk::QcowFile;
-use idsig::{V4Signature, HashAlgorithm};
-use log::{debug, error, warn, info};
+use idsig::{HashAlgorithm, V4Signature};
+use log::{debug, error, info, warn};
use microdroid_payload_config::VmPayloadConfig;
use rustutils::system_properties;
use std::convert::TryInto;
-use std::ffi::CString;
-use std::fs::{File, OpenOptions, create_dir};
+use std::fs::{create_dir, File, OpenOptions};
use std::io::{Error, ErrorKind, Write};
use std::num::NonZeroU32;
use std::os::unix::io::{FromRawFd, IntoRawFd};
@@ -557,11 +559,13 @@
#[derive(Debug)]
struct VirtualMachine {
instance: Arc<VmInstance>,
+ /// Keeps our service process running as long as this VM instance exists.
+ lazy_service_guard: LazyServiceGuard,
}
impl VirtualMachine {
fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
- let binder = VirtualMachine { instance };
+ let binder = VirtualMachine { instance, lazy_service_guard: Default::default() };
BnVirtualMachine::new_binder(binder, BinderFeatures::default())
}
}
@@ -714,19 +718,12 @@
/// 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))?;
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)
}
}
@@ -795,11 +792,6 @@
ParcelFileDescriptor::new(f)
}
-/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
- Status::new_exception(exception, CString::new(message.as_ref()).ok().as_deref())
-}
-
/// Simple utility for referencing Borrowed or Owned. Similar to std::borrow::Cow, but
/// it doesn't require that T implements Clone.
enum BorrowedOrOwned<'a, T> {
diff --git a/vm/src/main.rs b/vm/src/main.rs
index fe47d2c..062773b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -62,6 +62,11 @@
/// Whether to run VM in debug mode.
#[structopt(short, long)]
debug: bool,
+
+ /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+ /// in the VM config file.
+ #[structopt(short, long)]
+ mem: Option<u32>,
},
/// Run a virtual machine
Run {
@@ -118,7 +123,7 @@
.context("Failed to find VirtualizationService")?;
match opt {
- Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug } => {
+ Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug, mem } => {
command_run_app(
service,
&apk,
@@ -128,10 +133,11 @@
daemonize,
log.as_deref(),
debug,
+ mem,
)
}
Opt::Run { config, daemonize, log } => {
- command_run(service, &config, daemonize, log.as_deref())
+ command_run(service, &config, daemonize, log.as_deref(), /* mem */ None)
}
Opt::Stop { cid } => command_stop(service, cid),
Opt::List => command_list(service),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 0d34a97..42da6a3 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -49,6 +49,7 @@
daemonize: bool,
log_path: Option<&Path>,
debug: bool,
+ mem: Option<u32>,
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
@@ -76,8 +77,7 @@
instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
configPath: config_path.to_owned(),
debug,
- // Use the default.
- memoryMib: 0,
+ memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
});
run(service, &config, &format!("{:?}!{:?}", apk, config_path), daemonize, log_path)
}
@@ -88,10 +88,14 @@
config_path: &Path,
daemonize: bool,
log_path: Option<&Path>,
+ mem: Option<u32>,
) -> Result<(), Error> {
let config_file = File::open(config_path).context("Failed to open config file")?;
- let config =
+ let mut config =
VmConfig::load(&config_file).context("Failed to parse config file")?.to_parcelable()?;
+ if let Some(mem) = mem {
+ config.memoryMib = mem as i32;
+ }
run(
service,
&VirtualMachineConfig::RawConfig(config),