Panic on non-actionable failures
This is based on Michael's comments on aosp/2280849. For methods which
should never fail unless the VM is already dying, and for which
clients cannot take any meaningful action, panic instead of returning
false. Make sure we log the cause first.
Update client code to match. Update doc comments in the header file.
Also clarify that calling notify read more than once is harmless
(otherwise it would panic).
Incidentally, rename vs_payload_service.rs because it was confusing me
(we have a file of the same name in microdroid manager which actually
implements the service.)
Changes to AVmPayload_runVsockRpcServer will come later.
Bug: 243512108
Test: atest MicrodroidTests
Test: composd_cmd --test-compile
Change-Id: Ie6f6203ba54246cac669f4a68e8ab76f0a5792ae
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
new file mode 100644
index 0000000..febc2be
--- /dev/null
+++ b/vm_payload/src/api.rs
@@ -0,0 +1,235 @@
+// Copyright 2022, 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.
+
+//! This module handles the interaction with virtual machine payload service.
+
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+ IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
+use anyhow::{ensure, Context, Result};
+use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
+use lazy_static::lazy_static;
+use log::{error, info, Level};
+use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use std::ffi::CString;
+use std::fmt::Debug;
+use std::os::raw::{c_char, c_void};
+use std::ptr;
+use std::sync::{Mutex, atomic::{AtomicBool, Ordering}};
+
+lazy_static! {
+ static ref VM_APK_CONTENTS_PATH_C: CString =
+ CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
+ static ref PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::default();
+}
+
+static ALREADY_NOTIFIED: AtomicBool = AtomicBool::new(false);
+
+/// Return a connection to the payload service in Microdroid Manager. Uses the existing connection
+/// if there is one, otherwise attempts to create a new one.
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+ let mut connection = PAYLOAD_CONNECTION.lock().unwrap();
+ if let Some(strong) = &*connection {
+ Ok(strong.clone())
+ } else {
+ let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
+ VM_PAYLOAD_SERVICE_SOCKET_NAME,
+ )
+ .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+ *connection = Some(new_connection.clone());
+ Ok(new_connection)
+ }
+}
+
+/// Make sure our logging goes to logcat. It is harmless to call this more than once.
+fn initialize_logging() {
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Info),
+ );
+}
+
+/// In many cases clients can't do anything useful if API calls fail, and the failure
+/// generally indicates that the VM is exiting or otherwise doomed. So rather than
+/// returning a non-actionable error indication we just log the problem and abort
+/// the process.
+fn unwrap_or_abort<T, E: Debug>(result: Result<T, E>) -> T {
+ result.unwrap_or_else(|e| {
+ let msg = format!("{:?}", e);
+ error!("{msg}");
+ panic!("{msg}")
+ })
+}
+
+/// Notifies the host that the payload is ready.
+/// Panics on failure.
+#[no_mangle]
+pub extern "C" fn AVmPayload_notifyPayloadReady() {
+ initialize_logging();
+
+ if !ALREADY_NOTIFIED.swap(true, Ordering::Relaxed) {
+ unwrap_or_abort(try_notify_payload_ready());
+
+ info!("Notified host payload ready successfully");
+ }
+}
+
+/// Notifies the host that the payload is ready.
+/// Returns a `Result` containing error information if failed.
+fn try_notify_payload_ready() -> Result<()> {
+ get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
+}
+
+/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
+/// port.
+///
+/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
+/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
+/// attempt to connect.
+///
+/// The current thread is joined to the binder thread pool to handle incoming messages.
+///
+/// Returns true if the server has shutdown normally, false if it failed in some way.
+///
+/// # Safety
+///
+/// The `on_ready` callback is only called inside `run_vsock_rpc_server`, within the lifetime of
+/// `ReadyNotifier` (the last parameter of `run_vsock_rpc_server`). If `on_ready` is called with
+/// wrong param, the callback execution could go wrong.
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
+ service: *mut AIBinder,
+ port: u32,
+ on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
+ param: *mut c_void,
+) -> bool {
+ initialize_logging();
+
+ // SAFETY: AIBinder returned has correct reference count, and the ownership can
+ // safely be taken by new_spibinder.
+ let service = new_spibinder(service);
+ if let Some(service) = service {
+ match RpcServer::new_vsock(service, port) {
+ Ok(server) => {
+ if let Some(on_ready) = on_ready {
+ on_ready(param);
+ }
+ server.join();
+ true
+ }
+ Err(err) => {
+ error!("Failed to start RpcServer: {:?}", err);
+ false
+ }
+ }
+ } else {
+ error!("Failed to convert the given service from AIBinder to SpIBinder.");
+ false
+ }
+}
+
+/// Get a secret that is uniquely bound to this VM instance.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `identifier` must be [valid] for reads of `identifier_size` bytes.
+/// * `secret` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getVmInstanceSecret(
+ identifier: *const u8,
+ identifier_size: usize,
+ secret: *mut u8,
+ size: usize,
+) {
+ initialize_logging();
+
+ let identifier = std::slice::from_raw_parts(identifier, identifier_size);
+ let vm_secret = unwrap_or_abort(try_get_vm_instance_secret(identifier, size));
+ ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+}
+
+fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
+ let vm_secret = get_vm_payload_service()?
+ .getVmInstanceSecret(identifier, i32::try_from(size)?)
+ .context("Cannot get VM instance secret")?;
+ ensure!(
+ vm_secret.len() == size,
+ "Returned secret has {} bytes, expected {}",
+ vm_secret.len(),
+ size
+ );
+ Ok(vm_secret)
+}
+
+/// Get the VM's attestation chain.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getDiceAttestationChain(data: *mut u8, size: usize) -> usize {
+ initialize_logging();
+
+ let chain = unwrap_or_abort(try_get_dice_attestation_chain());
+ ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size));
+ chain.len()
+}
+
+fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
+ get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
+}
+
+/// Get the VM's attestation CDI.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getDiceAttestationCdi(data: *mut u8, size: usize) -> usize {
+ initialize_logging();
+
+ let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
+ ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size));
+ cdi.len()
+}
+
+fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
+ get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
+}
+
+/// Gets the path to the APK contents.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getApkContentsPath() -> *const c_char {
+ (*VM_APK_CONTENTS_PATH_C).as_ptr()
+}
+
+/// Gets the path to the VM's encrypted storage.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getEncryptedStoragePath() -> *const c_char {
+ // TODO(b/254454578): Return a real path if storage is present
+ ptr::null()
+}