Merge changes I9c148947,I4e4aea08 into main
* changes:
libfdt: Add FdtNodeMut::as_node()
pvmfw: Apply VM DTBO
diff --git a/README.md b/README.md
index eaa2579..3935f93 100644
--- a/README.md
+++ b/README.md
@@ -14,12 +14,14 @@
For in-depth explanations about individual topics and components, visit the following links.
AVF components:
-
* [pVM firmware](pvmfw/README.md)
* [Microdroid](microdroid/README.md)
* [Microdroid kernel](microdroid/kernel/README.md)
* [Microdroid payload](microdroid/payload/README.md)
* [vmbase](vmbase/README.md)
+
+AVF APIs:
+* [Java API](javalib/README.md)
* [VM Payload API](vm_payload/README.md)
How-Tos:
diff --git a/apex/Android.bp b/apex/Android.bp
index a05f7b0..a4c8861 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -58,6 +58,13 @@
"libvirtualizationservice_jni",
"libvirtualmachine_jni",
],
+ // TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
+ // Once the build system is fixed, remove this.
+ unwanted_transitive_deps: [
+ "libdrm",
+ "libsso",
+ "libutils",
+ ],
}
apex_defaults {
diff --git a/javalib/README.md b/javalib/README.md
new file mode 100644
index 0000000..4eb64df
--- /dev/null
+++ b/javalib/README.md
@@ -0,0 +1,364 @@
+# Android Virtualization Framework API
+
+These Java APIs allow an app to configure and run a Virtual Machine running
+[Microdroid](../microdroid/README.md) and execute native code from the app (the
+payload) within it.
+
+There is more information on AVF [here](../README.md). To see how to package the
+payload code that is to run inside a VM, and the native API available to it, see
+the [VM Payload API](../vm_payload/README.md)
+
+The API classes are all in the
+[`android.system.virtualmachine`](src/android/system/virtualmachine) package.
+
+Note that these APIs are all `@SystemApi` and require the restricted
+`android.permission.MANAGE_VIRTUAL_MACHINE` permission, so they are not
+available to third party apps.
+
+## Detecting AVF Support
+
+The simplest way to detect whether a device has support for AVF is to retrieve
+an instance of the
+[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java)
+class; if the result is not `null` then the device has support. You can then
+find out whether protected, non-protected VMs, or both are supported using the
+`getCapabilities()` method:
+
+```Java
+VirtualMachineManager vmm = context.getSystemService(VirtualMachineManager.class);
+if (vmm == null) {
+ // AVF is not supported.
+} else {
+ // AVF is supported.
+ int capabilities = vmm.getCapabilities();
+ if ((capabilties & CAPABILITY_PROTECTED_VM) != 0) {
+ // Protected VMs supported.
+ }
+ if ((capabilties & CAPABILITY_NON_PROTECTED_VM) != 0) {
+ // Non-Protected VMs supported.
+ }
+}
+```
+
+An alternative for detecting AVF support is to query support for the
+`android.software.virtualization_framework` system feature:
+
+```Java
+if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK)) {
+ // AVF is supported.
+}
+```
+
+You can also express a dependency on this system feature in your app's manifest
+with a
+[`<uses-feature>`](https://developer.android.com/guide/topics/manifest/uses-feature-element)
+element.
+
+
+## Starting a VM
+
+Once you have an instance of the
+[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java),
+a VM can be started by:
+- Specifying the desired VM configuration, using a
+ [`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java)
+ builder;
+- Creating a new
+ [`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java)
+ instance (or retrieving an existing one);
+- Registering to retrieve events from the VM by providing a
+ [`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java)
+ (optional, but recommended);
+- Running the VM.
+
+A minimal example might look like this:
+
+```Java
+VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(this)
+ .setProtectedVm(true)
+ .setPayloadBinaryName("my_payload.so")
+ .build();
+
+VirtualMachine vm = vmm.getOrCreate("my vm", config);
+
+vm.setCallback(executor, new VirtualMachineCallback() {...});
+
+vm.run();
+```
+
+Here we are running a protected VM, which will execute the code in the
+`my_payload.so` file included in your APK.
+
+Information about the VM, including its configuration, is stored in files in
+your app's private data directory. The file names are based on the VM name you
+supply. So once an instance of a VM has been created it can be retrieved by name
+even if the app is restarted or the device is rebooted. Directly inspecting or
+modifying these files is not recommended.
+
+The `getOrCreate()` call will retrieve an existing VM instance if it exists (in
+which case the `config` parameter is ignored), or create a new one
+otherwise. There are also separate `get()` and `create()` methods.
+
+The `run()` method is asynchronous; it returns successfully once the VM is
+starting. You can find out when the VM is ready, or if it fails, via your
+`VirtualMachineCallback` implementation.
+
+## VM Configuration
+
+There are other things that you can specify as part of the
+[`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java):
+- Whether the VM should be debuggable. A debuggable VM is not secure, but it
+ does allow access to logs from inside the VM, which can be useful for
+ troubleshooting.
+- How much memory should be available to the VM. (This is an upper bound;
+ typically memory is allocated to the VM as it is needed until the limit is
+ reached - but there is some overhead proportional to the maximum size.)
+- How many virtual CPUs the VM has.
+- How much encrypted storage the VM has.
+- The path to the installed APK containing the code to run as the VM payload.
+
+## VM Life-cycle
+
+To find out the progress of the Virtual Machine once it is started you should
+implement the methods defined by
+[`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java). These
+are called when the following events happen:
+- `onPayloadStarted()`: The VM payload is about to be run.
+- `onPayloadReady()`: The VM payload is running and ready to accept
+ connections. (This notification is triggered by the payload code, using the
+ [`AVmPayload_notifyPayloadReady()`](../vm_payload/include/vm_payload.h)
+ function.)
+- `onPayloadFinished()`: The VM payload has exited normally. The exit code of
+ the VM (the value returned by [`AVmPayload_main()`](../vm_payload/README.md))
+ is supplied as a parameter.
+- `onError()`: The VM failed; something went wrong. An error code and
+ human-readable message are provided which may help diagnosing the problem.
+- `onStopped()`: The VM is no longer running. This is the final notification
+ from any VM run, whether or not it was successful. You can run the VM again
+ when you want to. A reason code indicating why the VM stopped is supplied as a
+ parameter.
+
+You can also query the status of a VM at any point by calling `getStatus()` on
+the `VirtualMachine` object. This will return one of the following values:
+- `STATUS_STOPPED`: The VM is not running - either it has not yet been started,
+ or it stopped after running.
+- `STATUS_RUNNING`: The VM is running. Your payload inside the VM may not be
+ running, since the VM may be in the process of starting or stopping.
+- `STATUS_DELETED`: The VM has been deleted, e.g. by calling the `delete()`
+ method on
+ [`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java). This
+ is irreversible - once a VM is in this state it will never leave it.
+
+Some methods on
+[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java) can
+only be called when the VM status is `STATUS_RUNNING` (e.g. `stop()`), and some
+can only be called when the it is `STATUS_STOPPED` (e.g. `run()`).
+
+## VM Identity and Secrets
+
+Every VM has a 32-byte secret unique to it, which is not available to the
+host. We refer to this as the VM identity. The secret, and thus the identity,
+doesn’t normally change if the same VM is stopped and started, even after a
+reboot.
+
+In Android 14 the secret is derived, using the [Open Profile for
+DICE](https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md),
+from:
+- A device-specific randomly generated value;
+- The complete system image;
+- A per-instance salt;
+- The code running in the VM, including the bootloader, kernel, Microdroid and
+ payload;
+- Significant VM configuration options, e.g. whether the VM is debuggable.
+
+Any change to any of these will mean a different secret is generated. So while
+an attacker could start a similar VM with maliciously altered code, that VM will
+not have access to the same secret. An attempt to start an existing VM instance
+which doesn't derive the same secret will fail.
+
+However, this also means that if the payload code changes - for example, your
+app is updated - then this also changes the identity. An existing VM instance
+will no longer be runnable, and you will have to delete it and create a new
+instance with a new secret.
+
+The payload code is not given direct access to the VM secret, but an API is
+provided to allow deterministically deriving further secrets from it,
+e.g. encryption or signing keys. See
+[`AVmPayload_getVmInstanceSecret()`](../vm_payload/include/vm_payload.h).
+
+Some VM configuration changes are allowed that don’t affect the identity -
+e.g. changing the number of CPUs or the amount of memory allocated to the
+VM. This can be done using the `setConfig()` method on
+[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java).
+
+Deleting a VM (using the `delete()` method on
+[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java))
+and recreating it will generate a new salt, so the new VM will have a different
+secret, even if it is otherwise identical.
+
+## Communicating with a VM
+
+Once the VM payload has successfully started you will probably want to establish
+communication between it and your app.
+
+Only the app that started a VM can connect to it. The VM can accept connections
+from the app, but cannot initiate connections to other VMs or other processes in
+the host Android.
+
+### Vsock
+
+The simplest form of communication is using a socket running over the
+[vsock](https://man7.org/linux/man-pages/man7/vsock.7.html) protocol.
+
+We suggest that the VM payload should create a listening socket (using the
+standard socket API) and then trigger the `onPayloadReady()` callback; the app
+can then connect to the socket. This helps to avoid a race condition where the
+app tries to connect before the VM is listening, necessitating a retry
+mechanism.
+
+In the payload this might look like this:
+
+```C++
+#include "vm_payload.h"
+
+extern "C" int AVmPayload_main() {
+ int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ // bind, listen
+ AVmPayload_notifyPayloadReady();
+ // accept, read/write, ...
+}
+```
+
+And, in the app, like this:
+
+```Java
+void onPayloadReady(VirtualMachine vm) {
+ ParcelFileDescriptor pfd = vm.connectVsock(port);
+ // ...
+}
+```
+
+Vsock is useful for simple communication, or transferring of bulk data. For a
+richer RPC style of communication we suggest using Binder.
+
+### Binder
+
+The use of AIDL interfaces between the VM and app is support via Binder RPC,
+which transmits messages over an underlying vsock socket.
+
+Note that Binder RPC has some limitations compared to the kernel Binder used in
+Android - for example file descriptors can't be sent. It also isn't possible to
+send a kernel Binder interface over Binder RPC, or vice versa.
+
+There is a payload API to allow an AIDL interface to be served over a specific
+vsock port, and the VirtualMachine class provides a way to connect to the VM and
+retrieve an instance of the interface.
+
+The payload code to serve a hypothetical `IPayload` interface might look like
+this:
+
+```C++
+class PayloadImpl : public BnPayload { ... };
+
+
+extern "C" int AVmPayload_main() {
+ auto service = ndk::SharedRefBase::make<PayloadImpl>();
+ auto callback = [](void*) {
+ AVmPayload_notifyPayloadReady();
+ };
+ AVmPayload_runVsockRpcServer(service->asBinder().get(),
+ port, callback, nullptr);
+}
+
+```
+
+And then the app code to connect to it could look like this:
+
+```Java
+void onPayloadReady(VirtualMachine vm) {
+ IPayload payload =
+ Payload.Stub.asInterface(vm.connectToVsockServer(port));
+ // ...
+}
+```
+
+## Stopping a VM
+
+You can stop a VM abruptly by calling the `stop()` method on the
+[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java)
+instance. This is equivalent to turning off the power; the VM gets no
+opportunity to clean up at all. Any unwritten data might be lost.
+
+A better strategy might be to wait for the VM to exit cleanly (e.g. waiting for
+the `onStopped()` callback).
+
+Then you can arrange for your VM payload code to exit when it has finished its
+task (by returning from [`AVmPayload_main()`](../vm_payload/README.md), or
+calling `exit()`). Alternatively you could exit when you receive a request to do
+so from the app, e.g. via binder.
+
+When the VM payload does this you will receive an `onPayloadFinished()`
+callback, if you have installed a
+[`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java),
+which includes the payload's exit code.
+
+Use of `stop()` should be reserved as a recovery mechanism - for example if the
+VM has not stopped within a reasonable time after being requested to.
+
+The status of a VM will be `STATUS_STOPPED` after a successful call to `stop()`,
+or if your `onPayloadStopped()` callback is invoked.
+
+# Encrypted Storage
+
+When configuring a VM you can specify that it should have access to an encrypted
+storage filesystem of up to a specified size, using the
+`setEncryptedStorageBytes()` method on a
+[`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java)
+builder.
+
+Inside the VM this storage is mounted at a path that can be retrieved via the
+[`AVmPayload_getEncryptedStoragePath()`](../vm_payload/include/vm_payload.h)
+function. The VM can create sub-directories and read and write files here. Any
+data written is persisted and should be available next time the VM is run. (An
+automatic sync is done when the payload exits normally.)
+
+Outside the VM the storage is persisted as a file in the app’s private data
+directory. The data is encrypted using a key derived from the VM secret, which
+is not made available outside the VM.
+
+So an attacker should not be able to decrypt the data; however, a sufficiently
+powerful attacker could delete it, wholly or partially roll it back to an
+earlier version, or modify it, corrupting the data.
+
+# Transferring a VM
+
+It is possible to make a copy of a VM instance with a new name. This can be used
+to transfer a VM from one app to another, which can be useful in some
+circumstances.
+
+This should only be done while the VM is stopped. The first step is to call
+`toDescriptor()` on the
+[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java)
+instance, which returns a
+[`VirtualMachineDescriptor`](src/android/system/virtualmachine/VirtualMachineDescriptor.java)
+object. This object internally contains open file descriptors to the files that
+hold the VM's state (its instance data, configuration, and encrypted storage).
+
+A `VirtualMachineDescriptor` is
+[`Parcelable`](https://developer.android.com/reference/android/os/Parcelable),
+so it can be passed to another app via a Binder call. Any app with a
+`VirtualMachineDescriptor` can pass it, along with a new VM name, to the
+`importFromDescriptor()` method on
+[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java). This
+is equivalent to calling `create()` with the same name and configuration, except
+that the new VM is the same instance as the original, with the same VM secret,
+and has access to a copy of the original's encrypted storage.
+
+Once the transfer has been completed it would be reasonable to delete the
+original VM, using the `delete()` method on `VirtualMachineManager`.
+
+
+
+
+
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index 343552d..bc306ad 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -17,14 +17,19 @@
//! Integration tests of the library libfdt.
use libfdt::{Fdt, FdtError, FdtNodeMut, Phandle};
-use std::ffi::{CStr, CString};
+use std::ffi::CString;
use std::fs;
use std::ops::Range;
// TODO(b/308694211): Use cstr!() from vmbase
macro_rules! cstr {
($str:literal) => {{
- CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
+ const S: &str = concat!($str, "\0");
+ const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+ Ok(v) => v,
+ Err(_) => panic!("string contains interior NUL"),
+ };
+ C
}};
}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index c257cdb..f5f3f15 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -31,7 +31,7 @@
exec_start init_debug_policy
on init
- mkdir /mnt/apk 0755 system system
+ mkdir /mnt/apk 0755 root root
mkdir /mnt/extra-apk 0755 root root
# Allow the payload access to the console (default is 0600)
@@ -40,9 +40,6 @@
# Microdroid_manager starts apkdmverity/zipfuse/apexd
start microdroid_manager
- # restorecon so microdroid_manager can create subdirectories
- restorecon /mnt/extra-apk
-
# Wait for apexd to finish activating APEXes before starting more processes.
# Microdroid starts apexd in VM mode in which apexd doesn't wait for init after setting
# apexd.status to activated, but immediately transitions to ready. Therefore, it's not safe to
diff --git a/pvmfw/src/bootargs.rs b/pvmfw/src/bootargs.rs
index e4b1b65..a089a67 100644
--- a/pvmfw/src/bootargs.rs
+++ b/pvmfw/src/bootargs.rs
@@ -94,7 +94,7 @@
// after.
let name_end = arg.find(|c: char| c.is_whitespace() || c == '=').unwrap_or(arg.len());
let (arg, equal_sign) = match arg.chars().nth(name_end) {
- Some(c) if c == '=' => {
+ Some('=') => {
let value_end = name_end + Self::find_value_end(&arg[name_end..]);
(&arg[..value_end], Some(name_end))
}
@@ -109,9 +109,16 @@
mod tests {
use super::*;
+ // TODO(b/308694211): Use cstr!() from vmbase
macro_rules! cstr {
($str:literal) => {{
- core::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
+ const S: &str = concat!($str, "\0");
+ const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes())
+ {
+ Ok(v) => v,
+ Err(_) => panic!("string contains interior NUL"),
+ };
+ C
}};
}
diff --git a/vm_payload/README.md b/vm_payload/README.md
index d5f5331..ec4dc59 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -8,8 +8,8 @@
Note that only native code is supported in Microdroid, so no Java APIs are
available in the VM, and only 64 bit code is supported.
-To create a VM and run the payload from Android, see
-[android.system.virtualmachine.VirtualMachineManager](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/javalib/src/android/system/virtualmachine/VirtualMachineManager.java).
+To create a VM and run the payload from Android see the [AVF Java
+APIs](../javalib/README.md).
## Entry point
diff --git a/vmbase/src/util.rs b/vmbase/src/util.rs
index 7fe6015..25586bc 100644
--- a/vmbase/src/util.rs
+++ b/vmbase/src/util.rs
@@ -20,7 +20,12 @@
#[macro_export]
macro_rules! cstr {
($str:literal) => {{
- core::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
+ const S: &str = concat!($str, "\0");
+ const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+ Ok(v) => v,
+ Err(_) => panic!("string contains interior NUL"),
+ };
+ C
}};
}