Merge "Ferrochrome launcher" into main
diff --git a/Android.bp b/Android.bp
index 3b6b8b5..b2af69e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -77,6 +77,7 @@
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_dice_changes",
+ "release_avf_enable_network",
"release_avf_enable_vendor_modules",
"release_avf_enable_virt_cpufreq",
],
@@ -91,6 +92,9 @@
release_avf_enable_dice_changes: {
cflags: ["-DAVF_OPEN_DICE_CHANGES=1"],
},
+ release_avf_enable_network: {
+ cflags: ["-DAVF_ENABLE_NETWORK=1"],
+ },
release_avf_enable_vendor_modules: {
cflags: ["-DAVF_ENABLE_VENDOR_MODULES=1"],
},
diff --git a/apex/Android.bp b/apex/Android.bp
index 17b1f9e..8a53a3d 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -89,6 +89,7 @@
],
jni_libs: [
"libvirtualizationservice_jni",
+ "libvirtualizationsystemservice_jni",
"libvirtualmachine_jni",
],
// TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
diff --git a/apex/product_packages.mk b/apex/product_packages.mk
index a318817..486334c 100644
--- a/apex/product_packages.mk
+++ b/apex/product_packages.mk
@@ -56,3 +56,9 @@
$(error RELEASE_AVF_ENABLE_DICE_CHANGES must also be enabled)
endif
endif
+
+ifdef RELEASE_AVF_ENABLE_NETWORK
+ ifndef RELEASE_AVF_ENABLE_LLPVM_CHANGES
+ $(error RELEASE_AVF_ENABLE_LLPVM_CHANGES must also be enabled)
+ endif
+endif
diff --git a/compos/Android.bp b/compos/Android.bp
index b840506..220533a 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -25,7 +25,7 @@
"librpcbinder_rs",
"librustutils",
"libscopeguard",
- "libvm_payload_bindgen",
+ "libvm_payload_rs",
],
prefer_rlib: true,
shared_libs: [
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 06cc599..9bc522c 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -23,13 +23,9 @@
mod fsverity;
use anyhow::Result;
-use binder::unstable_api::AsNative;
use compos_common::COMPOS_VSOCK_PORT;
use log::{debug, error};
-use std::os::raw::c_void;
use std::panic;
-use std::ptr;
-use vm_payload_bindgen::{AIBinder, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer};
fn main() {
if let Err(e) = try_main() {
@@ -50,17 +46,5 @@
}));
debug!("compsvc is starting as a rpc service.");
- let param = ptr::null_mut();
- let mut service = compsvc::new_binder()?.as_binder();
- let service = service.as_native_mut() as *mut AIBinder;
- // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
- // is the same type as sys::AIBinder. It is safe for on_ready to be invoked at any time, with
- // any parameter.
- unsafe { AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param) }
-}
-
-extern "C" fn on_ready(_param: *mut c_void) {
- // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
- // call at any time.
- unsafe { AVmPayload_notifyPayloadReady() };
+ vm_payload::run_single_vsock_service(compsvc::new_binder()?, COMPOS_VSOCK_PORT)
}
diff --git a/flags/cpp/include/android/avf_cc_flags.h b/flags/cpp/include/android/avf_cc_flags.h
index c922266..70be925 100644
--- a/flags/cpp/include/android/avf_cc_flags.h
+++ b/flags/cpp/include/android/avf_cc_flags.h
@@ -27,6 +27,14 @@
#endif
}
+inline bool IsNetworkFlagEnabled() {
+#ifdef AVF_ENABLE_NETWORK
+ return AVF_ENABLE_NETWORK;
+#else
+ return false;
+#endif
+}
+
inline bool IsVendorModulesFlagEnabled() {
#ifdef AVF_ENABLE_VENDOR_MODULES
return AVF_ENABLE_VENDOR_MODULES;
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index 8b444fc..4446b29 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -103,6 +103,7 @@
private static final String KEY_EXTRA_APKS = "extraApks";
private static final String KEY_NETWORK_SUPPORTED = "networkSupported";
private static final String KEY_SHOULD_BOOST_UCLAMP = "shouldBoostUclamp";
+ private static final String KEY_SHOULD_USE_HUGEPAGES = "shouldUseHugepages";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -215,6 +216,8 @@
private final boolean mShouldBoostUclamp;
+ private final boolean mShouldUseHugepages;
+
@Retention(RetentionPolicy.SOURCE)
@StringDef(
prefix = "MICRODROID",
@@ -251,7 +254,8 @@
@Nullable File vendorDiskImage,
@NonNull @OsName String os,
boolean networkSupported,
- boolean shouldBoostUclamp) {
+ boolean shouldBoostUclamp,
+ boolean shouldUseHugepages) {
// This is only called from Builder.build(); the builder handles parameter validation.
mPackageName = packageName;
mApkPath = apkPath;
@@ -276,6 +280,7 @@
mOs = os;
mNetworkSupported = networkSupported;
mShouldBoostUclamp = shouldBoostUclamp;
+ mShouldUseHugepages = shouldUseHugepages;
}
/** Loads a config from a file. */
@@ -379,6 +384,8 @@
builder.setNetworkSupported(b.getBoolean(KEY_NETWORK_SUPPORTED));
builder.setShouldBoostUclamp(b.getBoolean(KEY_SHOULD_BOOST_UCLAMP));
+ builder.setShouldUseHugepages(b.getBoolean(KEY_SHOULD_USE_HUGEPAGES));
+
return builder.build();
}
@@ -431,6 +438,7 @@
}
b.putBoolean(KEY_NETWORK_SUPPORTED, mNetworkSupported);
b.putBoolean(KEY_SHOULD_BOOST_UCLAMP, mShouldBoostUclamp);
+ b.putBoolean(KEY_SHOULD_USE_HUGEPAGES, mShouldUseHugepages);
b.writeToStream(output);
}
@@ -793,6 +801,8 @@
}
vsConfig.boostUclamp = mShouldBoostUclamp;
+ vsConfig.hugePages = mShouldUseHugepages;
+
return vsConfig;
}
@@ -874,6 +884,7 @@
@NonNull @OsName private String mOs = DEFAULT_OS;
private boolean mNetworkSupported;
private boolean mShouldBoostUclamp = false;
+ private boolean mShouldUseHugepages = false;
/**
* Creates a builder for the given context.
@@ -973,7 +984,8 @@
mVendorDiskImage,
mOs,
mNetworkSupported,
- mShouldBoostUclamp);
+ mShouldBoostUclamp,
+ mShouldUseHugepages);
}
/**
@@ -1301,5 +1313,11 @@
mShouldBoostUclamp = shouldBoostUclamp;
return this;
}
+
+ /** @hide */
+ public Builder setShouldUseHugepages(boolean shouldUseHugepages) {
+ mShouldUseHugepages = shouldUseHugepages;
+ return this;
+ }
}
}
diff --git a/java/jni/Android.bp b/java/jni/Android.bp
index 4a569d4..d9b1880 100644
--- a/java/jni/Android.bp
+++ b/java/jni/Android.bp
@@ -20,6 +20,20 @@
}
cc_library_shared {
+ name: "libvirtualizationsystemservice_jni",
+ defaults: ["avf_build_flags_cc"],
+ srcs: [
+ "com_android_system_virtualmachine_VirtualizationSystemService.cpp",
+ ],
+ apex_available: ["com.android.virt"],
+ shared_libs: [
+ "liblog",
+ "libnativehelper",
+ ],
+ static_libs: ["libavf_cc_flags"],
+}
+
+cc_library_shared {
name: "libvirtualmachine_jni",
defaults: ["avf_build_flags_cc"],
srcs: [
diff --git a/java/jni/com_android_system_virtualmachine_VirtualizationSystemService.cpp b/java/jni/com_android_system_virtualmachine_VirtualizationSystemService.cpp
new file mode 100644
index 0000000..a15e7a7
--- /dev/null
+++ b/java/jni/com_android_system_virtualmachine_VirtualizationSystemService.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#define LOG_TAG "VirtualizationSystemService"
+
+#include <android/avf_cc_flags.h>
+#include <jni.h>
+#include <log/log.h>
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_android_system_virtualmachine_VirtualizationSystemService_nativeIsNetworkFlagEnabled(
+ [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
+ return android::virtualization::IsNetworkFlagEnabled();
+}
diff --git a/java/service/Android.bp b/java/service/Android.bp
index 8bac7be..814445c 100644
--- a/java/service/Android.bp
+++ b/java/service/Android.bp
@@ -31,6 +31,7 @@
],
static_libs: [
"android.system.virtualizationmaintenance-java",
+ "android.system.vmtethering-java",
],
sdk_version: "core_platform",
apex_available: ["com.android.virt"],
diff --git a/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index 2461755..970f780 100644
--- a/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -26,6 +26,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.system.virtualizationmaintenance.IVirtualizationMaintenance;
+import android.system.vmtethering.IVmTethering;
import android.util.Log;
import com.android.internal.os.BackgroundThread;
@@ -39,18 +40,35 @@
* storing secrets for apps or users that no longer exist.
*/
public class VirtualizationSystemService extends SystemService {
+ static {
+ System.loadLibrary("virtualizationsystemservice_jni");
+ }
+
private static final String TAG = VirtualizationSystemService.class.getName();
- private static final String SERVICE_NAME = "android.system.virtualizationmaintenance";
+ private static final String MAINTENANCE_SERVICE_NAME =
+ "android.system.virtualizationmaintenance";
private Handler mHandler;
+ private final TetheringService mTetheringService;
+
+ /*
+ * Retrieve boolean value whether RELEASE_AVF_ENABLE_NETWORK build flag is enabled or not.
+ */
+ static native boolean nativeIsNetworkFlagEnabled();
public VirtualizationSystemService(Context context) {
super(context);
+ if (nativeIsNetworkFlagEnabled()) {
+ mTetheringService = new TetheringService();
+ } else {
+ mTetheringService = null;
+ }
}
@Override
public void onStart() {
- // Nothing needed here - we don't expose any binder service. The binder service we use is
- // exposed as a lazy service by the virtualizationservice native binary.
+ if (mTetheringService != null) {
+ publishBinderService(IVmTethering.DESCRIPTOR, mTetheringService);
+ }
}
@Override
@@ -82,11 +100,11 @@
}
static IVirtualizationMaintenance connectToMaintenanceService() {
- IBinder binder = ServiceManager.waitForService(SERVICE_NAME);
+ IBinder binder = ServiceManager.waitForService(MAINTENANCE_SERVICE_NAME);
IVirtualizationMaintenance maintenance =
IVirtualizationMaintenance.Stub.asInterface(binder);
if (maintenance == null) {
- throw new IllegalStateException("Failed to connect to " + SERVICE_NAME);
+ throw new IllegalStateException("Failed to connect to " + MAINTENANCE_SERVICE_NAME);
}
return maintenance;
}
@@ -136,4 +154,11 @@
}
}
}
+
+ private static final class TetheringService extends IVmTethering.Stub {
+ @Override
+ public void enableVmTethering() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("VM tethering is not supported yet");
+ }
+ }
}
diff --git a/service_vm/demo_apk/Android.bp b/service_vm/demo_apk/Android.bp
index 3750fe6..c64b70a 100644
--- a/service_vm/demo_apk/Android.bp
+++ b/service_vm/demo_apk/Android.bp
@@ -23,7 +23,7 @@
"libandroid_logger",
"libanyhow",
"liblog_rust",
- "libvm_payload_bindgen",
+ "libvm_payload_rs",
],
}
diff --git a/service_vm/demo_apk/src/main.rs b/service_vm/demo_apk/src/main.rs
index 8ea4e65..26df52c 100644
--- a/service_vm/demo_apk/src/main.rs
+++ b/service_vm/demo_apk/src/main.rs
@@ -14,25 +14,15 @@
//! Main executable of Service VM client for manual testing.
-use anyhow::{anyhow, ensure, Result};
+use anyhow::{ensure, Context, Result};
use log::{error, info};
-use std::{
- ffi::{c_void, CStr},
- panic,
- ptr::{self, NonNull},
- result,
-};
-use vm_payload_bindgen::{
- AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
- AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey,
- AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString,
- AVmPayload_requestAttestation,
-};
+use std::panic;
+use vm_payload::AttestationError;
+
+vm_payload::main!(main);
/// Entry point of the Service VM client.
-#[allow(non_snake_case)]
-#[no_mangle]
-pub extern "C" fn AVmPayload_main() {
+fn main() {
android_logger::init_once(
android_logger::Config::default()
.with_tag("service_vm_client")
@@ -52,15 +42,11 @@
info!("Welcome to Service VM Client!");
let too_big_challenge = &[0u8; 66];
- let res = AttestationResult::request_attestation(too_big_challenge);
+ let res = vm_payload::request_attestation(too_big_challenge);
ensure!(res.is_err());
- let status = res.unwrap_err();
- ensure!(
- status == AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE,
- "Unexpected status: {:?}",
- status
- );
- info!("Status: {:?}", status_to_cstr(status));
+ let error = res.unwrap_err();
+ ensure!(error == AttestationError::InvalidChallenge, "Unexpected error: {error:?}");
+ info!("Error: {error}");
// The data below is only a placeholder generated randomly with urandom
let challenge = &[
@@ -68,162 +54,18 @@
0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d, 0xc8, 0x07,
0x11, 0x7b,
];
- let res = AttestationResult::request_attestation(challenge)
- .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))?;
+ let res = vm_payload::request_attestation(challenge).context("Unexpected attestation error")?;
- let cert_chain = res.certificate_chain()?;
+ let cert_chain: Vec<_> = res.certificate_chain().collect();
info!("Attestation result certificateChain = {:?}", cert_chain);
- let private_key = res.private_key()?;
+ let private_key = res.private_key();
info!("Attestation result privateKey = {:?}", private_key);
let message = b"Hello from Service VM client";
info!("Signing message: {:?}", message);
- let signature = res.sign(message)?;
+ let signature = res.sign_message(message);
info!("Signature: {:?}", signature);
Ok(())
}
-
-#[derive(Debug)]
-struct AttestationResult(NonNull<AVmAttestationResult>);
-
-impl AttestationResult {
- fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
- let mut res: *mut AVmAttestationResult = ptr::null_mut();
- // SAFETY: It is safe as we only read the challenge within its bounds and the
- // function does not retain any reference to it.
- let status = unsafe {
- AVmPayload_requestAttestation(
- challenge.as_ptr() as *const c_void,
- challenge.len(),
- &mut res,
- )
- };
- if status == AVmAttestationStatus::ATTESTATION_OK {
- info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
- let res = NonNull::new(res).expect("The attestation result is null");
- Ok(Self(res))
- } else {
- Err(status)
- }
- }
-
- fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
- let num_certs = get_certificate_count(self.as_ref());
- let mut certs = Vec::with_capacity(num_certs);
- for i in 0..num_certs {
- certs.push(get_certificate_at(self.as_ref(), i)?);
- }
- Ok(certs)
- }
-
- fn private_key(&self) -> Result<Box<[u8]>> {
- get_private_key(self.as_ref())
- }
-
- fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
- sign_with_attested_key(self.as_ref(), message)
- }
-}
-
-impl AsRef<AVmAttestationResult> for AttestationResult {
- fn as_ref(&self) -> &AVmAttestationResult {
- // SAFETY: This field is private, and only populated with a successful call to
- // `AVmPayload_requestAttestation`.
- unsafe { self.0.as_ref() }
- }
-}
-
-impl Drop for AttestationResult {
- fn drop(&mut self) {
- // SAFETY: This field is private, and only populated with a successful call to
- // `AVmPayload_requestAttestation`, and not freed elsewhere.
- unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
- }
-}
-
-fn get_certificate_count(res: &AVmAttestationResult) -> usize {
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getCertificateCount(res) }
-}
-
-fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
- let size =
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
- let mut cert = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `cert`.
- // And `cert` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_getCertificateAt(
- res,
- index,
- cert.as_mut_ptr() as *mut c_void,
- cert.len(),
- )
- };
- ensure!(size == cert.len());
- Ok(cert.into_boxed_slice())
-}
-
-fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
- let size =
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
- let mut private_key = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `private_key`.
- // And `private_key` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_getPrivateKey(
- res,
- private_key.as_mut_ptr() as *mut c_void,
- private_key.len(),
- )
- };
- ensure!(size == private_key.len());
- Ok(private_key.into_boxed_slice())
-}
-
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- let size = unsafe {
- AVmAttestationResult_sign(
- res,
- message.as_ptr() as *const c_void,
- message.len(),
- ptr::null_mut(),
- 0,
- )
- };
- let mut signature = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `signature`.
- // And `signature` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_sign(
- res,
- message.as_ptr() as *const c_void,
- message.len(),
- signature.as_mut_ptr() as *mut c_void,
- signature.len(),
- )
- };
- ensure!(size == signature.len());
- Ok(signature.into_boxed_slice())
-}
-
-fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
- // SAFETY: The function only reads the given enum status and returns a pointer to a
- // static string.
- let message = unsafe { AVmAttestationStatus_toString(status) };
- // SAFETY: The pointer returned by `AVmAttestationStatus_toString` is guaranteed to
- // point to a valid C String that lives forever.
- unsafe { CStr::from_ptr(message) }
-}
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
index 1ba156f..58b394a 100644
--- a/service_vm/test_apk/Android.bp
+++ b/service_vm/test_apk/Android.bp
@@ -39,7 +39,7 @@
"libanyhow",
"libavflog",
"liblog_rust",
- "libvm_payload_bindgen",
+ "libvm_payload_rs",
],
}
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
index 00ddff8..52635ad 100644
--- a/service_vm/test_apk/src/native/main.rs
+++ b/service_vm/test_apk/src/native/main.rs
@@ -14,35 +14,26 @@
//! Main executable of VM attestation for end-to-end testing.
-use anyhow::{anyhow, ensure, Result};
+use anyhow::Result;
use avflog::LogResult;
use com_android_virt_vm_attestation_testservice::{
aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
AttestationStatus::AttestationStatus, BnAttestationService, IAttestationService,
SigningResult::SigningResult, PORT,
},
- binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
+ binder::{self, BinderFeatures, Interface, IntoBinderResult, Strong},
};
use log::{error, info};
use std::{
- ffi::{c_void, CStr},
panic,
- ptr::{self, NonNull},
- result,
sync::{Arc, Mutex},
};
-use vm_payload_bindgen::{
- AIBinder, AVmAttestationResult, AVmAttestationResult_free,
- AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
- AVmAttestationResult_getPrivateKey, AVmAttestationResult_sign, AVmAttestationStatus,
- AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
- AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
-};
+use vm_payload::{AttestationError, AttestationResult};
-/// Entry point of the Service VM client.
-#[allow(non_snake_case)]
-#[no_mangle]
-pub extern "C" fn AVmPayload_main() {
+vm_payload::main!(main);
+
+// Entry point of the Service VM client.
+fn main() {
android_logger::init_once(
android_logger::Config::default()
.with_tag("service_vm_client")
@@ -61,18 +52,7 @@
fn try_main() -> Result<()> {
info!("Welcome to Service VM Client!");
- let mut service = AttestationService::new_binder().as_binder();
- let service = service.as_native_mut() as *mut AIBinder;
- let param = ptr::null_mut();
- // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
- // is the same type as `sys::AIBinder`. It is safe for `on_ready` to be invoked at any time,
- // with any parameter.
- unsafe { AVmPayload_runVsockRpcServer(service, PORT.try_into()?, Some(on_ready), param) };
-}
-
-extern "C" fn on_ready(_param: *mut c_void) {
- // SAFETY: It is safe to call `AVmPayload_notifyPayloadReady` at any time.
- unsafe { AVmPayload_notifyPayloadReady() };
+ vm_payload::run_single_vsock_service(AttestationService::new_binder(), PORT.try_into()?)
}
struct AttestationService {
@@ -88,11 +68,11 @@
}
}
+#[allow(non_snake_case)]
impl IAttestationService for AttestationService {
fn requestAttestationForTesting(&self) -> binder::Result<()> {
const CHALLENGE: &[u8] = &[0xaa; 32];
- let res = AttestationResult::request_attestation_for_testing(CHALLENGE)
- .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+ let res = vm_payload::restricted::request_attestation_for_testing(CHALLENGE)
.with_log()
.or_service_specific_exception(-1)?;
*self.res.lock().unwrap() = Some(res);
@@ -104,218 +84,46 @@
challenge: &[u8],
message: &[u8],
) -> binder::Result<SigningResult> {
- let res = match AttestationResult::request_attestation(challenge) {
+ let res: AttestationResult = match vm_payload::request_attestation(challenge) {
Ok(res) => res,
- Err(status) => {
- let status = to_attestation_status(status);
+ Err(e) => {
+ let status = to_attestation_status(e);
return Ok(SigningResult { certificateChain: vec![], signature: vec![], status });
}
};
- let certificate_chain =
- res.certificate_chain().with_log().or_service_specific_exception(-1)?;
+
+ let certificate_chain: Vec<u8> = res.certificate_chain().flatten().collect();
let status = AttestationStatus::OK;
- let signature = res.sign(message).with_log().or_service_specific_exception(-1)?;
+ let signature = res.sign_message(message);
+
Ok(SigningResult { certificateChain: certificate_chain, signature, status })
}
fn validateAttestationResult(&self) -> binder::Result<()> {
// TODO(b/191073073): Returns the attestation result to the host for validation.
- self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
- }
-}
-
-fn to_attestation_status(status: AVmAttestationStatus) -> AttestationStatus {
- match status {
- AVmAttestationStatus::ATTESTATION_OK => AttestationStatus::OK,
- AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => {
- AttestationStatus::ERROR_INVALID_CHALLENGE
- }
- AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => {
- AttestationStatus::ERROR_ATTESTATION_FAILED
- }
- AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => AttestationStatus::ERROR_UNSUPPORTED,
- }
-}
-
-#[derive(Debug)]
-struct AttestationResult(NonNull<AVmAttestationResult>);
-
-// Safety: `AttestationResult` is not `Send` because it contains a raw pointer to a C struct.
-unsafe impl Send for AttestationResult {}
-
-impl AttestationResult {
- fn request_attestation_for_testing(
- challenge: &[u8],
- ) -> result::Result<Self, AVmAttestationStatus> {
- let mut res: *mut AVmAttestationResult = ptr::null_mut();
- // SAFETY: It is safe as we only read the challenge within its bounds and the
- // function does not retain any reference to it.
- let status = unsafe {
- AVmPayload_requestAttestationForTesting(
- challenge.as_ptr() as *const c_void,
- challenge.len(),
- &mut res,
- )
- };
- if status == AVmAttestationStatus::ATTESTATION_OK {
- info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
- let res = NonNull::new(res).expect("The attestation result is null");
- Ok(Self(res))
- } else {
- Err(status)
- }
- }
-
- fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
- let mut res: *mut AVmAttestationResult = ptr::null_mut();
- // SAFETY: It is safe as we only read the challenge within its bounds and the
- // function does not retain any reference to it.
- let status = unsafe {
- AVmPayload_requestAttestation(
- challenge.as_ptr() as *const c_void,
- challenge.len(),
- &mut res,
- )
- };
- if status == AVmAttestationStatus::ATTESTATION_OK {
- info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
- let res = NonNull::new(res).expect("The attestation result is null");
- Ok(Self(res))
- } else {
- Err(status)
- }
- }
-
- fn certificate_chain(&self) -> Result<Vec<u8>> {
- let num_certs = get_certificate_count(self.as_ref());
- let mut certs = Vec::new();
- for i in 0..num_certs {
- certs.extend(get_certificate_at(self.as_ref(), i)?.iter());
- }
- Ok(certs)
- }
-
- fn private_key(&self) -> Result<Box<[u8]>> {
- get_private_key(self.as_ref())
- }
-
- fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
- sign_with_attested_key(self.as_ref(), message)
- }
-
- fn log(&self) -> Result<()> {
- let cert_chain = self.certificate_chain()?;
- info!("Attestation result certificateChain = {:?}", cert_chain);
-
- let private_key = self.private_key()?;
- info!("Attestation result privateKey = {:?}", private_key);
-
- let message = b"Hello from Service VM client";
- info!("Signing message: {:?}", message);
- let signature = self.sign(message)?;
- info!("Signature: {:?}", signature);
+ log(self.res.lock().unwrap().as_ref().unwrap());
Ok(())
}
}
-impl AsRef<AVmAttestationResult> for AttestationResult {
- fn as_ref(&self) -> &AVmAttestationResult {
- // SAFETY: This field is private, and only populated with a successful call to
- // `AVmPayload_requestAttestation`.
- unsafe { self.0.as_ref() }
+fn log(res: &AttestationResult) {
+ for (i, cert) in res.certificate_chain().enumerate() {
+ info!("Attestation result certificate {i} = {cert:?}");
}
+
+ let private_key = res.private_key();
+ info!("Attestation result privateKey = {private_key:?}");
+
+ let message = b"Hello from Service VM client";
+ info!("Signing message: {message:?}");
+ let signature = res.sign_message(message);
+ info!("Signature: {signature:?}");
}
-impl Drop for AttestationResult {
- fn drop(&mut self) {
- // SAFETY: This field is private, and only populated with a successful call to
- // `AVmPayload_requestAttestation`, and not freed elsewhere.
- unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
+fn to_attestation_status(e: AttestationError) -> AttestationStatus {
+ match e {
+ AttestationError::InvalidChallenge => AttestationStatus::ERROR_INVALID_CHALLENGE,
+ AttestationError::AttestationFailed => AttestationStatus::ERROR_ATTESTATION_FAILED,
+ AttestationError::AttestationUnsupported => AttestationStatus::ERROR_UNSUPPORTED,
}
}
-
-fn get_certificate_count(res: &AVmAttestationResult) -> usize {
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getCertificateCount(res) }
-}
-
-fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
- let size =
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
- let mut cert = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `cert`.
- // And `cert` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_getCertificateAt(
- res,
- index,
- cert.as_mut_ptr() as *mut c_void,
- cert.len(),
- )
- };
- ensure!(size == cert.len());
- Ok(cert.into_boxed_slice())
-}
-
-fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
- let size =
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
- let mut private_key = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `private_key`.
- // And `private_key` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_getPrivateKey(
- res,
- private_key.as_mut_ptr() as *mut c_void,
- private_key.len(),
- )
- };
- ensure!(size == private_key.len());
- Ok(private_key.into_boxed_slice())
-}
-
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Vec<u8>> {
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed.
- let size = unsafe {
- AVmAttestationResult_sign(
- res,
- message.as_ptr() as *const c_void,
- message.len(),
- ptr::null_mut(),
- 0,
- )
- };
- let mut signature = vec![0u8; size];
- // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
- // before getting freed. This function only writes within the bounds of `signature`.
- // And `signature` cannot overlap `res` because we just allocated it.
- let size = unsafe {
- AVmAttestationResult_sign(
- res,
- message.as_ptr() as *const c_void,
- message.len(),
- signature.as_mut_ptr() as *mut c_void,
- signature.len(),
- )
- };
- ensure!(size <= signature.len());
- signature.truncate(size);
- Ok(signature)
-}
-
-fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
- // SAFETY: The function only reads the given enum status and returns a pointer to a
- // static string.
- let message = unsafe { AVmAttestationStatus_toString(status) };
- // SAFETY: The pointer returned by `AVmAttestationStatus_toString` is guaranteed to
- // point to a valid C String that lives forever.
- unsafe { CStr::from_ptr(message) }
-}
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 639de06..c33d3f5 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -16,8 +16,8 @@
package com.android.microdroid.benchmark;
-import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
@@ -34,10 +34,10 @@
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.Process;
import android.os.RemoteException;
+import android.system.Os;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineException;
-import android.system.Os;
import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
@@ -158,6 +158,7 @@
newVmConfigBuilderWithPayloadBinary("MicrodroidIdleNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
.setMemoryBytes(mem * ONE_MEBI)
+ .setShouldUseHugepages(true)
.build();
// returns true if succeeded at least once.
@@ -234,6 +235,7 @@
VirtualMachineConfig.Builder builder =
newVmConfigBuilderWithPayloadBinary("MicrodroidIdleNativeLib.so")
.setShouldBoostUclamp(true)
+ .setShouldUseHugepages(true)
.setMemoryBytes(256 * ONE_MEBI)
.setDebugLevel(DEBUG_LEVEL_NONE);
if (fullDebug) {
@@ -347,6 +349,7 @@
newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
.setDebugLevel(DEBUG_LEVEL_NONE)
.setShouldBoostUclamp(true)
+ .setShouldUseHugepages(true)
.build();
List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
@@ -373,6 +376,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.build();
List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
@@ -524,6 +528,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.setMemoryBytes(256 * ONE_MEBI)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
@@ -610,6 +615,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.setMemoryBytes(256 * ONE_MEBI)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
@@ -729,6 +735,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.setShouldBoostUclamp(true)
.build();
@@ -778,6 +785,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.build();
List<Double> requestLatencies = new ArrayList<>(IO_TEST_TRIAL_COUNT * NUM_REQUESTS);
@@ -836,6 +844,7 @@
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
.setDebugLevel(DEBUG_LEVEL_NONE)
+ .setShouldUseHugepages(true)
.build();
List<Double> vmKillTime = new ArrayList<>(TEST_TRIAL_COUNT);
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 6474965..026cf3f 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -191,6 +191,9 @@
assume().withMessage("Skip where protected VMs aren't supported")
.that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
.isNotEqualTo(0);
+ assume().withMessage("Testing protected VMs on GSI isn't supported. b/272443823")
+ .that(isGsi())
+ .isFalse();
} else {
assume().withMessage("Skip where VMs aren't supported")
.that(capabilities & VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM)
@@ -212,13 +215,17 @@
.that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
.isTrue();
int vendorApiLevel = getVendorApiLevel();
- boolean isGsi = new File("/system/system_ext/etc/init/init.gsi.rc").exists();
+ boolean isGsi = isGsi();
Log.i(TAG, "isGsi = " + isGsi + ", vendor api level = " + vendorApiLevel);
assume().withMessage("GSI with vendor API level < 202404 may not support AVF")
.that(isGsi && vendorApiLevel < 202404)
.isFalse();
}
+ protected boolean isGsi() {
+ return new File("/system/system_ext/etc/init/init.gsi.rc").exists();
+ }
+
protected static int getVendorApiLevel() {
return SystemProperties.getInt("ro.board.api_level", 0);
}
@@ -549,6 +556,7 @@
public int mFileMode;
public int mMountFlags;
public String mConsoleInput;
+ public byte[] mInstanceSecret;
public void assertNoException() {
if (mException != null) {
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 471aea7..e32ff88 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -55,6 +55,7 @@
"MicrodroidExitNativeLib",
"MicrodroidPrivateLinkingNativeLib",
"MicrodroidCrashNativeLib",
+ "libmicrodroid_testlib_rust",
"libvm_attestation_test_payload",
],
min_sdk_version: "33",
@@ -166,3 +167,22 @@
header_libs: ["vm_payload_headers"],
stl: "libc++_static",
}
+
+// A payload written in Rust, using the Rust wrapper for the VM payload API.
+rust_ffi_shared {
+ name: "libmicrodroid_testlib_rust",
+ crate_name: "microdroid_testlib_rust",
+ defaults: ["avf_build_flags_rust"],
+ prefer_rlib: true,
+ srcs: ["src/native/testbinary.rs"],
+ compile_multilib: "both",
+ rustlibs: [
+ "com.android.microdroid.testservice-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "libavflog",
+ "libcstr",
+ "liblog_rust",
+ "libvm_payload_rs",
+ ],
+}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 4141903..4d0f5eb 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -197,6 +197,7 @@
tr.mSublibRunProp = ts.readProperty("debug.microdroid.app.sublib.run");
tr.mApkContentsPath = ts.getApkContentsPath();
tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
+ tr.mInstanceSecret = ts.insecurelyExposeVmInstanceSecret();
});
testResults.assertNoException();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
@@ -204,6 +205,7 @@
assertThat(testResults.mSublibRunProp).isEqualTo("true");
assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+ assertThat(testResults.mInstanceSecret).hasLength(32);
}
@Test
@@ -2363,6 +2365,63 @@
runVmTestService(TAG, vm, (ts, tr) -> {}).assertNoException();
}
+ @Test
+ public void createAndRunRustVm() throws Exception {
+ // This test is here mostly to exercise the Rust wrapper around the VM Payload API.
+ // We're testing the same functionality as in other tests, the only difference is
+ // that the payload is written in Rust.
+
+ assumeSupportedDevice();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilderWithPayloadBinary("libmicrodroid_testlib_rust.so")
+ .setMemoryBytes(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("rust_vm", config);
+
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mAddInteger = ts.addInteger(37, 73);
+ tr.mApkContentsPath = ts.getApkContentsPath();
+ tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
+ tr.mInstanceSecret = ts.insecurelyExposeVmInstanceSecret();
+ });
+ testResults.assertNoException();
+ assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
+ assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+ assertThat(testResults.mInstanceSecret).hasLength(32);
+ }
+
+ @Test
+ public void createAndRunRustVmWithEncryptedStorage() throws Exception {
+ // This test is here mostly to exercise the Rust wrapper around the VM Payload API.
+ // We're testing the same functionality as in other tests, the only difference is
+ // that the payload is written in Rust.
+
+ assumeSupportedDevice();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilderWithPayloadBinary("libmicrodroid_testlib_rust.so")
+ .setMemoryBytes(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("rust_vm", config);
+
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> tr.mEncryptedStoragePath = ts.getEncryptedStoragePath());
+ testResults.assertNoException();
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
+ }
+
private VirtualMachineConfig buildVmConfigWithVendor(File vendorDiskImage) throws Exception {
return buildVmConfigWithVendor(vendorDiskImage, "MicrodroidTestNativeLib.so");
}
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
new file mode 100644
index 0000000..85b411e
--- /dev/null
+++ b/tests/testapk/src/native/testbinary.rs
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! A VM payload that exists to allow testing of the Rust wrapper for the VM payload APIs.
+
+use anyhow::Result;
+use com_android_microdroid_testservice::{
+ aidl::com::android::microdroid::testservice::{
+ IAppCallback::IAppCallback,
+ ITestService::{BnTestService, ITestService, PORT},
+ },
+ binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong},
+};
+use cstr::cstr;
+use log::{error, info};
+use std::panic;
+use std::process::exit;
+use std::string::String;
+use std::vec::Vec;
+
+vm_payload::main!(main);
+
+// Entry point of the Service VM client.
+fn main() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("microdroid_testlib_rust")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+ // Redirect panic messages to logcat.
+ panic::set_hook(Box::new(|panic_info| {
+ error!("{panic_info}");
+ }));
+ if let Err(e) = try_main() {
+ error!("failed with {:?}", e);
+ exit(1);
+ }
+}
+
+fn try_main() -> Result<()> {
+ info!("Welcome to the Rust test binary");
+
+ vm_payload::run_single_vsock_service(TestService::new_binder(), PORT.try_into()?)
+}
+
+struct TestService {}
+
+impl Interface for TestService {}
+
+impl TestService {
+ fn new_binder() -> Strong<dyn ITestService> {
+ BnTestService::new_binder(TestService {}, BinderFeatures::default())
+ }
+}
+
+impl ITestService for TestService {
+ fn quit(&self) -> BinderResult<()> {
+ exit(0)
+ }
+
+ fn addInteger(&self, a: i32, b: i32) -> BinderResult<i32> {
+ a.checked_add(b).ok_or_else(|| Status::new_exception(ExceptionCode::ILLEGAL_ARGUMENT, None))
+ }
+
+ fn getApkContentsPath(&self) -> BinderResult<String> {
+ Ok(vm_payload::apk_contents_path().to_string_lossy().to_string())
+ }
+
+ fn getEncryptedStoragePath(&self) -> BinderResult<String> {
+ Ok(vm_payload::encrypted_storage_path()
+ .map(|p| p.to_string_lossy().to_string())
+ .unwrap_or("".to_string()))
+ }
+
+ fn insecurelyExposeVmInstanceSecret(&self) -> BinderResult<Vec<u8>> {
+ let mut secret = vec![0u8; 32];
+ vm_payload::get_vm_instance_secret(b"identifier", secret.as_mut_slice());
+ Ok(secret)
+ }
+
+ // Everything below here is unimplemented. Implementations may be added as needed.
+
+ fn readProperty(&self, _: &str) -> BinderResult<String> {
+ unimplemented()
+ }
+ fn insecurelyExposeAttestationCdi(&self) -> BinderResult<Vec<u8>> {
+ unimplemented()
+ }
+ fn getBcc(&self) -> BinderResult<Vec<u8>> {
+ unimplemented()
+ }
+ fn runEchoReverseServer(&self) -> BinderResult<()> {
+ unimplemented()
+ }
+ fn getEffectiveCapabilities(&self) -> BinderResult<Vec<String>> {
+ unimplemented()
+ }
+ fn getUid(&self) -> BinderResult<i32> {
+ unimplemented()
+ }
+ fn writeToFile(&self, _: &str, _: &str) -> BinderResult<()> {
+ unimplemented()
+ }
+ fn readFromFile(&self, _: &str) -> BinderResult<String> {
+ unimplemented()
+ }
+ fn getFilePermissions(&self, _: &str) -> BinderResult<i32> {
+ unimplemented()
+ }
+ fn getMountFlags(&self, _: &str) -> BinderResult<i32> {
+ unimplemented()
+ }
+ fn requestCallback(&self, _: &Strong<dyn IAppCallback + 'static>) -> BinderResult<()> {
+ unimplemented()
+ }
+ fn readLineFromConsole(&self) -> BinderResult<String> {
+ unimplemented()
+ }
+}
+
+fn unimplemented<T>() -> BinderResult<T> {
+ let message = cstr!("Got a call to an unimplemented ITestService method in testbinary.rs");
+ error!("{message:?}");
+ Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, Some(message)))
+}
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 0c39501..f9034af 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -30,6 +30,7 @@
"android.system.virtualizationservice-rust",
"android.system.virtualizationservice_internal-rust",
"android.system.virtualmachineservice-rust",
+ "android.system.vmtethering-rust",
"android.os.permissions_aidl-rust",
"libandroid_logger",
"libanyhow",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index fb89772..bca4512 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -86,6 +86,26 @@
}
aidl_interface {
+ name: "android.system.vmtethering",
+ srcs: ["android/system/vmtethering/**/*.aidl"],
+ unstable: true,
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ apex_available: [
+ "com.android.virt",
+ ],
+ },
+ rust: {
+ enabled: true,
+ apex_available: [
+ "com.android.virt",
+ ],
+ },
+ },
+}
+
+aidl_interface {
name: "android.system.virtualmachineservice",
srcs: ["android/system/virtualmachineservice/**/*.aidl"],
imports: [
diff --git a/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl b/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl
new file mode 100644
index 0000000..732a515
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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.vmtethering;
+
+interface IVmTethering {
+ /**
+ * Start VM tethering to provide external network to VM.
+ */
+ void enableVmTethering();
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 70da37b..c0e1cc7 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -25,6 +25,7 @@
use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
use android_system_virtualizationservice_internal as android_vs_internal;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice;
+use android_system_vmtethering::aidl::android::system::vmtethering;
use android_vs_internal::aidl::android::system::virtualizationservice_internal;
use anyhow::{anyhow, ensure, Context, Result};
use avflog::LogResult;
@@ -73,6 +74,7 @@
IVmnic::{BpVmnic, IVmnic},
};
use virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
+use vmtethering::IVmTethering::{BpVmTethering, IVmTethering};
use vsock::{VsockListener, VsockStream};
/// The unique ID of a VM used (together with a port number) for vsock communication.
@@ -163,6 +165,9 @@
static ref NETWORK_SERVICE: Strong<dyn IVmnic> =
wait_for_interface(<BpVmnic as IVmnic>::get_descriptor())
.expect("Could not connect to Vmnic");
+ static ref TETHERING_SERVICE: Strong<dyn IVmTethering> =
+ wait_for_interface(<BpVmTethering as IVmTethering>::get_descriptor())
+ .expect("Could not connect to VmTethering");
}
fn is_valid_guest_cid(cid: Cid) -> bool {
@@ -523,7 +528,20 @@
))
.with_log();
}
- NETWORK_SERVICE.createTapInterface(iface_name_suffix)
+ let tap_fd = NETWORK_SERVICE.createTapInterface(iface_name_suffix)?;
+
+ // TODO(340377643): Due to lack of implementation of creating bridge interface, tethering is
+ // enabled for TAP interface instead of bridge interface. After introducing creation of
+ // bridge interface in AVF, we should modify it.
+ if let Err(e) = TETHERING_SERVICE.enableVmTethering() {
+ if e.exception_code() == ExceptionCode::UNSUPPORTED_OPERATION {
+ warn!("{}", e.get_description());
+ } else {
+ return Err(e);
+ }
+ }
+
+ Ok(tap_fd)
}
fn deleteTapInterface(&self, tap_fd: &ParcelFileDescriptor) -> binder::Result<()> {
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index 229f533..cf2a002 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -39,8 +39,8 @@
visibility: [":__subpackages__"],
}
-// Rust wrappers round the C API for Rust clients.
-// (Yes, this involves going Rust -> C -> Rust.)
+// Access to the C API for Rust code.
+// This shouldn't be used directly - prefer libvm_payload_rs (below)
rust_bindgen {
name: "libvm_payload_bindgen",
wrapper_src: "include-restricted/vm_payload_restricted.h",
@@ -51,15 +51,31 @@
bindgen_flags: [
"--default-enum-style rust",
],
- visibility: [
- "//packages/modules/Virtualization/compos",
- "//packages/modules/Virtualization/service_vm:__subpackages__",
- ],
shared_libs: [
"libvm_payload#current",
],
}
+// Wrapper library for the raw C API for use by Rust clients.
+// (Yes, this involves going Rust -> C -> Rust.)
+// This is not a stable API - we may change it in subsequent versions.
+// But it is made available as an rlib so it is linked into any
+// code using it, leaving only dependencies on stable APIs.
+// So code built with it should run unchanged on future versions.
+rust_library_rlib {
+ name: "libvm_payload_rs",
+ crate_name: "vm_payload",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["wrapper/lib.rs"],
+ rustlibs: [
+ "libbinder_rs",
+ "libstatic_assertions",
+ "libvm_payload_bindgen",
+ ],
+ apex_available: ["com.android.compos"],
+ visibility: ["//visibility:public"],
+}
+
// Shared library for clients to link against.
cc_library_shared {
name: "libvm_payload",
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 4b1e6f3..66fd532 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -70,3 +70,16 @@
See [AIDL
backends](https://source.android.com/docs/core/architecture/aidl/aidl-backends)
for information on using AIDL with the NDK Binder from C++.
+
+## Rust
+
+A Rust wrapper library for the VM Payload API is available (as an rlib) for VM
+payloads written in Rust.
+
+This wrapper is not guaranteed to be stable; we may change it in future
+versions. But payload code built using it will depend only on the C VM Payload
+API and the NDK APIs that are available to the payload, so should run unchanged
+on future versions.
+
+See [wrapper/lib.rs](wrapper/lib.rs) and `libvm_payload_rs` in
+[Android.bp](Android.bp).
diff --git a/vm_payload/wrapper/attestation.rs b/vm_payload/wrapper/attestation.rs
new file mode 100644
index 0000000..e0055d5
--- /dev/null
+++ b/vm_payload/wrapper/attestation.rs
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::error::Error;
+use std::ffi::{c_void, CStr};
+use std::fmt::{self, Display};
+use std::iter::FusedIterator;
+use std::ptr::{self, NonNull};
+
+use vm_payload_bindgen::{
+ AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
+ AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey,
+ AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString,
+ AVmPayload_requestAttestation, AVmPayload_requestAttestationForTesting,
+};
+
+/// Holds the result of a successful Virtual Machine attestation request.
+/// See [`request_attestation`].
+#[derive(Debug)]
+pub struct AttestationResult {
+ result: NonNull<AVmAttestationResult>,
+}
+
+/// Error type that can be returned from an unsuccessful Virtual Machine attestation request.
+/// See [`request_attestation`].
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+pub enum AttestationError {
+ /// The challenge size was not between 0 and 64 bytes (inclusive).
+ InvalidChallenge,
+ /// The attempt to attest the VM failed. A subsequent request may succeed.
+ AttestationFailed,
+ /// VM attestation is not supported in the current environment.
+ AttestationUnsupported,
+}
+
+impl Error for AttestationError {}
+
+impl Display for AttestationError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ let status = match self {
+ Self::InvalidChallenge => AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE,
+ Self::AttestationFailed => AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED,
+ Self::AttestationUnsupported => AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED,
+ };
+ // SAFETY: AVmAttestationStatus_toString always returns a non-null pointer to a
+ // nul-terminated C string with static lifetime (which is valid UTF-8).
+ let c_str = unsafe { CStr::from_ptr(AVmAttestationStatus_toString(status)) };
+ let str = c_str.to_str().expect("Invalid UTF-8 for AVmAttestationStatus");
+ f.write_str(str)
+ }
+}
+
+impl Drop for AttestationResult {
+ fn drop(&mut self) {
+ let ptr = self.result.as_ptr();
+
+ // SAFETY: The `result` field is private, and only populated with a successful call to
+ // `AVmPayload_requestAttestation`, and not freed elsewhere.
+ unsafe { AVmAttestationResult_free(ptr) };
+ }
+}
+
+// SAFETY: The API functions that accept the `AVmAttestationResult` pointer are all safe to call
+// from any thread, including `AVmAttestationResult_free` which is called only on drop.
+unsafe impl Send for AttestationResult {}
+
+// SAFETY: There is no interior mutation here; any future functions that might mutate the data would
+// require a non-const pointer and hence need `&mut self` here. The only existing such function is
+// `AVmAttestationResult_free` where we take a mutable reference guaranteeing no other references
+// exist. The raw API functions are safe to call from any thread.
+unsafe impl Sync for AttestationResult {}
+
+/// Requests the remote attestation of this VM.
+///
+/// On success the supplied [`challenge`] will be included in the certificate chain accessible from
+/// the [`AttestationResult`]; this can be used as proof of the freshness of the attestation.
+///
+/// The challenge should be no more than 64 bytes long or the request will fail.
+pub fn request_attestation(challenge: &[u8]) -> Result<AttestationResult, AttestationError> {
+ let mut result: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: We only read the challenge within its bounds and the function does not retain any
+ // reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestation(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut result,
+ )
+ };
+ AttestationResult::new(status, result)
+}
+
+/// A variant of [`request_attestation`] used for testing purposes. This should not be used by
+/// normal VMs, and is not available to app owned VMs.
+pub fn request_attestation_for_testing(
+ challenge: &[u8],
+) -> Result<AttestationResult, AttestationError> {
+ let mut result: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: We only read the challenge within its bounds and the function does not retain any
+ // reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestationForTesting(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut result,
+ )
+ };
+ AttestationResult::new(status, result)
+}
+
+impl AttestationResult {
+ fn new(
+ status: AVmAttestationStatus,
+ result: *mut AVmAttestationResult,
+ ) -> Result<AttestationResult, AttestationError> {
+ match status {
+ AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => {
+ Err(AttestationError::InvalidChallenge)
+ }
+ AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => {
+ Err(AttestationError::AttestationFailed)
+ }
+ AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => {
+ Err(AttestationError::AttestationUnsupported)
+ }
+ AVmAttestationStatus::ATTESTATION_OK => {
+ let result = NonNull::new(result)
+ .expect("Attestation succeeded but the attestation result is null");
+ Ok(AttestationResult { result })
+ }
+ }
+ }
+
+ fn as_const_ptr(&self) -> *const AVmAttestationResult {
+ self.result.as_ptr().cast_const()
+ }
+
+ /// Returns the attested private key. This is the ECDSA P-256 private key corresponding to the
+ /// public key described by the leaf certificate in the attested
+ /// [certificate chain](AttestationResult::certificate_chain). It is a DER-encoded
+ /// `ECPrivateKey` structure as specified in
+ /// [RFC 5915 s3](https://datatracker.ietf.org/doc/html/rfc5915#section-3).
+ ///
+ /// Note: The [`sign_message`](AttestationResult::sign_message) method allows signing with the
+ /// key without retrieving it.
+ pub fn private_key(&self) -> Vec<u8> {
+ let ptr = self.as_const_ptr();
+
+ let size =
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+ // writes no data since we pass a zero size, and null is explicitly allowed for the
+ // destination in that case.
+ unsafe { AVmAttestationResult_getPrivateKey(ptr, ptr::null_mut(), 0) };
+
+ let mut private_key = vec![0u8; size];
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+ // writes within the bounds of `private_key`, which we just allocated so cannot be aliased.
+ let size = unsafe {
+ AVmAttestationResult_getPrivateKey(
+ ptr,
+ private_key.as_mut_ptr() as *mut c_void,
+ private_key.len(),
+ )
+ };
+ assert_eq!(size, private_key.len());
+ private_key
+ }
+
+ /// Signs the given message using the attested private key. The signature uses ECDSA P-256; the
+ /// message is first hashed with SHA-256 and then it is signed with the attested EC P-256
+ /// [private key](AttestationResult::private_key).
+ ///
+ /// The signature is a DER-encoded `ECDSASignature`` structure as described in
+ /// [RFC 6979](https://datatracker.ietf.org/doc/html/rfc6979).
+ pub fn sign_message(&self, message: &[u8]) -> Vec<u8> {
+ let ptr = self.as_const_ptr();
+
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+ // writes no data since we pass a zero size, and null is explicitly allowed for the
+ // destination in that case.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ ptr,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ ptr::null_mut(),
+ 0,
+ )
+ };
+
+ let mut signature = vec![0u8; size];
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+ // writes within the bounds of `signature`, which we just allocated so cannot be aliased.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ ptr,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ signature.as_mut_ptr() as *mut c_void,
+ signature.len(),
+ )
+ };
+ assert!(size <= signature.len());
+ signature.truncate(size);
+ signature
+ }
+
+ /// Returns an iterator over the certificates forming the certificate chain for the VM, and its
+ /// public key, obtained by the attestation process.
+ ///
+ /// The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+ /// the attestation key's certificate chain. It starts with the leaf certificate covering the
+ /// attested public key and ends with the root certificate.
+ pub fn certificate_chain(&self) -> CertIterator {
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid.
+ let count = unsafe { AVmAttestationResult_getCertificateCount(self.as_const_ptr()) };
+
+ CertIterator { result: self, count, current: 0 }
+ }
+
+ fn certificate(&self, index: usize) -> Vec<u8> {
+ let ptr = self.as_const_ptr();
+
+ let size =
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function
+ // writes no data since we pass a zero size, and null is explicitly allowed for the
+ // destination in that case. The function will panic if `index` is out of range (which
+ // is safe).
+ unsafe { AVmAttestationResult_getCertificateAt(ptr, index, ptr::null_mut(), 0) };
+
+ let mut cert = vec![0u8; size];
+ // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only
+ // writes within the bounds of `cert`, which we just allocated so cannot be aliased.
+ let size = unsafe {
+ AVmAttestationResult_getCertificateAt(
+ ptr,
+ index,
+ cert.as_mut_ptr() as *mut c_void,
+ cert.len(),
+ )
+ };
+ assert_eq!(size, cert.len());
+ cert
+ }
+}
+
+/// An iterator over the DER-encoded X.509 certificates containin in an [`AttestationResult`].
+/// See [`certificate_chain`](AttestationResult::certificate_chain) for more details.
+pub struct CertIterator<'a> {
+ result: &'a AttestationResult,
+ count: usize,
+ current: usize, // Invariant: current <= count
+}
+
+impl<'a> Iterator for CertIterator<'a> {
+ type Item = Vec<u8>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.current < self.count {
+ let cert = self.result.certificate(self.current);
+ self.current += 1;
+ Some(cert)
+ } else {
+ None
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let size = self.count - self.current;
+ (size, Some(size))
+ }
+}
+
+impl<'a> ExactSizeIterator for CertIterator<'a> {}
+impl<'a> FusedIterator for CertIterator<'a> {}
diff --git a/vm_payload/wrapper/lib.rs b/vm_payload/wrapper/lib.rs
new file mode 100644
index 0000000..d3f03d7
--- /dev/null
+++ b/vm_payload/wrapper/lib.rs
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2024 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 wrapper for the VM Payload API, allowing virtual machine payload code to be written in
+//! Rust. This wraps the raw C API, accessed via bindgen, into a more idiomatic Rust interface.
+//!
+//! See `https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/vm_payload/README.md`
+//! for more information on the VM Payload API.
+
+mod attestation;
+
+pub use attestation::{request_attestation, AttestationError, AttestationResult};
+use binder::unstable_api::AsNative;
+use binder::{FromIBinder, Strong};
+use std::ffi::{c_void, CStr, OsStr};
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+use std::ptr;
+use vm_payload_bindgen::{
+ AIBinder, AVmPayload_getApkContentsPath, AVmPayload_getEncryptedStoragePath,
+ AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer,
+};
+
+/// The functions declared here are restricted to VMs created with a config file;
+/// they will fail, or panic, if called in other VMs. The ability to create such VMs
+/// requires the android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission, and is
+/// therefore not available to privileged or third party apps.
+///
+/// These functions can be used by tests, if the permission is granted via shell.
+pub mod restricted {
+ pub use crate::attestation::request_attestation_for_testing;
+}
+
+/// Marks the main function of the VM payload.
+///
+/// When the VM is run, this function is called. If it returns, the VM ends normally with a 0 exit
+/// code.
+///
+/// Example:
+///
+/// ```rust
+/// use log::info;
+///
+/// vm_payload::main!(vm_main);
+///
+/// fn vm_main() {
+/// android_logger::init_once(
+/// android_logger::Config::default()
+/// .with_tag("example_vm_payload")
+/// .with_max_level(log::LevelFilter::Info),
+/// );
+/// info!("Hello world");
+/// }
+/// ```
+#[macro_export]
+macro_rules! main {
+ ($name:path) => {
+ // Export a symbol with a name matching the extern declaration below.
+ #[export_name = "rust_main"]
+ fn __main() {
+ // Ensure that the main function provided by the application has the correct type.
+ $name()
+ }
+ };
+}
+
+// This is the real C entry point for the VM; we just forward to the Rust entry point.
+#[allow(non_snake_case)]
+#[no_mangle]
+extern "C" fn AVmPayload_main() {
+ extern "Rust" {
+ fn rust_main();
+ }
+
+ // SAFETY: rust_main is provided by the application using the `main!` macro above, which makes
+ // sure it has the right type.
+ unsafe { rust_main() }
+}
+
+/// Notifies the host that the payload is ready.
+///
+/// If the host app has set a `VirtualMachineCallback` for the VM, its
+/// `onPayloadReady` method will be called.
+///
+/// Note that subsequent calls to this function after the first have no effect;
+/// `onPayloadReady` is never called more than once.
+pub fn notify_payload_ready() {
+ // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
+ // call at any time.
+ unsafe { AVmPayload_notifyPayloadReady() };
+}
+
+/// 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 (i.e. it is listening on the port),
+/// [`notify_payload_ready`] is called to notify the host that the server is ready. This is
+/// appropriate for VM payloads that serve a single binder service - which is common.
+///
+/// Note that this function does not return. The calling thread joins the binder
+/// thread pool to handle incoming messages.
+pub fn run_single_vsock_service<T>(service: Strong<T>, port: u32) -> !
+where
+ T: FromIBinder + ?Sized,
+{
+ extern "C" fn on_ready(_param: *mut c_void) {
+ notify_payload_ready();
+ }
+
+ let mut service = service.as_binder();
+ // The cast here is needed because the compiler doesn't know that our vm_payload_bindgen
+ // AIBinder is the same type as binder_ndk_sys::AIBinder.
+ let service = service.as_native_mut() as *mut AIBinder;
+ let param = ptr::null_mut();
+ // SAFETY: We have a strong reference to the service, so the raw pointer remains valid. It is
+ // safe for on_ready to be invoked at any time, with any parameter.
+ unsafe { AVmPayload_runVsockRpcServer(service, port, Some(on_ready), param) }
+}
+
+/// Gets the path to the contents of the APK containing the VM payload. It is a directory, under
+/// which are the unzipped contents of the APK containing the payload, all read-only
+/// but accessible to the payload.
+pub fn apk_contents_path() -> &'static Path {
+ // SAFETY: AVmPayload_getApkContentsPath always returns a non-null pointer to a
+ // nul-terminated C string with static lifetime.
+ let c_str = unsafe { CStr::from_ptr(AVmPayload_getApkContentsPath()) };
+ Path::new(OsStr::from_bytes(c_str.to_bytes()))
+}
+
+/// Gets the path to the encrypted persistent storage for the VM, if any. This is
+/// a directory under which any files or directories created will be stored on
+/// behalf of the VM by the host app. All data is encrypted using a key known
+/// only to the VM, so the host cannot decrypt it, but may delete it.
+///
+/// Returns `None` if no encrypted storage was requested in the VM configuration.
+pub fn encrypted_storage_path() -> Option<&'static Path> {
+ // SAFETY: AVmPayload_getEncryptedStoragePath returns either null or a pointer to a
+ // nul-terminated C string with static lifetime.
+ let ptr = unsafe { AVmPayload_getEncryptedStoragePath() };
+ if ptr.is_null() {
+ None
+ } else {
+ // SAFETY: We know the pointer is not null, and so it is a valid C string.
+ let c_str = unsafe { CStr::from_ptr(ptr) };
+ Some(Path::new(OsStr::from_bytes(c_str.to_bytes())))
+ }
+}
+
+/// Retrieves all or part of a 32-byte secret that is bound to this unique VM
+/// instance and the supplied identifier. The secret can be used e.g. as an
+/// encryption key.
+///
+/// Every VM has a secret that is derived from a device-specific value known to
+/// the hypervisor, the code that runs in the VM and its non-modifiable
+/// configuration; it is not made available to the host OS.
+///
+/// This function performs a further derivation from the VM secret and the
+/// supplied identifier. As long as the VM identity doesn't change the same value
+/// will be returned for the same identifier, even if the VM is stopped &
+/// restarted or the device rebooted.
+///
+/// If multiple secrets are required for different purposes, a different
+/// identifier should be used for each. The identifiers otherwise are arbitrary
+/// byte sequences and do not need to be kept secret; typically they are
+/// hardcoded in the calling code.
+///
+/// The secret is returned in [`secret`], truncated to its size, which must be between
+/// 1 and 32 bytes (inclusive) or the function will panic.
+pub fn get_vm_instance_secret(identifier: &[u8], secret: &mut [u8]) {
+ let secret_size = secret.len();
+ assert!((1..=32).contains(&secret_size), "VM instance secrets can be up to 32 bytes long");
+
+ // SAFETY: The function only reads from `[identifier]` within its bounds, and only writes to
+ // `[secret]` within its bounds. Neither reference is retained, and we know neither is null.
+ unsafe {
+ AVmPayload_getVmInstanceSecret(
+ identifier.as_ptr() as *const c_void,
+ identifier.len(),
+ secret.as_mut_ptr() as *mut c_void,
+ secret_size,
+ )
+ }
+}