1# Android Virtualization Framework API 2 3These Java APIs allow an app to configure and run a Virtual Machine running 4[Microdroid](../microdroid/README.md) and execute native code from the app (the 5payload) within it. 6 7There is more information on AVF [here](../README.md). To see how to package the 8payload code that is to run inside a VM, and the native API available to it, see 9the [VM Payload API](../vm_payload/README.md) 10 11The API classes are all in the 12[`android.system.virtualmachine`](src/android/system/virtualmachine) package. 13 14All of these APIs were introduced in API level 34 (Android 14). The classes may 15not exist in devices running an earlier version. 16 17Note that they are all `@SystemApi` and require the restricted 18`android.permission.MANAGE_VIRTUAL_MACHINE` permission, so they are not 19available to third party apps. In Android 14 the permission was available only to 20privileged apps; in Android 15 it is available to all preinstalled apps. On both 21versions it can also be granted to other apps via `adb shell pm grant` for 22development purposes. 23 24 25## Detecting AVF Support 26 27The simplest way to detect whether a device has support for AVF is to retrieve 28an instance of the 29[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java) 30class; if the result is not `null` then the device has support. You can then 31find out whether protected, non-protected VMs, or both are supported using the 32`getCapabilities()` method. Note that this code requires API level 34 or higher: 33 34```Java 35VirtualMachineManager vmm = context.getSystemService(VirtualMachineManager.class); 36if (vmm == null) { 37 // AVF is not supported. 38} else { 39 // AVF is supported. 40 int capabilities = vmm.getCapabilities(); 41 if ((capabilties & CAPABILITY_PROTECTED_VM) != 0) { 42 // Protected VMs supported. 43 } 44 if ((capabilties & CAPABILITY_NON_PROTECTED_VM) != 0) { 45 // Non-Protected VMs supported. 46 } 47} 48``` 49 50An alternative for detecting AVF support is to query support for the 51`android.software.virtualization_framework` system feature. This method will 52work on any API level, and return false if it is below 34: 53 54```Java 55if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK)) { 56 // AVF is supported. 57} 58``` 59 60You can also express a dependency on this system feature in your app's manifest 61with a 62[`<uses-feature>`](https://developer.android.com/guide/topics/manifest/uses-feature-element) 63element. 64 65 66## Starting a VM 67 68Once you have an instance of the 69[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java), 70a VM can be started by: 71- Specifying the desired VM configuration, using a 72 [`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java) 73 builder; 74- Creating a new 75 [`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java) 76 instance (or retrieving an existing one); 77- Registering to retrieve events from the VM by providing a 78 [`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java) 79 (optional, but recommended); 80- Running the VM. 81 82A minimal example might look like this: 83 84```Java 85VirtualMachineConfig config = 86 new VirtualMachineConfig.Builder(this) 87 .setProtectedVm(true) 88 .setPayloadBinaryName("my_payload.so") 89 .build(); 90 91VirtualMachine vm = vmm.getOrCreate("my vm", config); 92 93vm.setCallback(executor, new VirtualMachineCallback() {...}); 94 95vm.run(); 96``` 97 98Here we are running a protected VM, which will execute the code in the 99`my_payload.so` file included in your APK. 100 101Information about the VM, including its configuration, is stored in files in 102your app's private data directory. The file names are based on the VM name you 103supply. So once an instance of a VM has been created it can be retrieved by name 104even if the app is restarted or the device is rebooted. Directly inspecting or 105modifying these files is not recommended. 106 107The `getOrCreate()` call will retrieve an existing VM instance if it exists (in 108which case the `config` parameter is ignored), or create a new one 109otherwise. There are also separate `get()` and `create()` methods. 110 111The `run()` method is asynchronous; it returns successfully once the VM is 112starting. You can find out when the VM is ready, or if it fails, via your 113`VirtualMachineCallback` implementation. 114 115## VM Configuration 116 117There are other things that you can specify as part of the 118[`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java): 119- Whether the VM should be debuggable. A debuggable VM is not secure, but it 120 does allow access to logs from inside the VM, which can be useful for 121 troubleshooting. 122- How much memory should be available to the VM. (This is an upper bound; 123 typically memory is allocated to the VM as it is needed until the limit is 124 reached - but there is some overhead proportional to the maximum size.) 125- How many virtual CPUs the VM has. 126- How much encrypted storage the VM has. 127- The path to the installed APK containing the code to run as the VM 128 payload. (Normally you don't need this; the APK path is determined from the 129 context passed to the config builder.) 130 131## VM Life-cycle 132 133To find out the progress of the Virtual Machine once it is started you should 134implement the methods defined by 135[`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java). These 136are called when the following events happen: 137- `onPayloadStarted()`: The VM payload is about to be run. 138- `onPayloadReady()`: The VM payload is running and ready to accept 139 connections. (This notification is triggered by the payload code, using the 140 [`AVmPayload_notifyPayloadReady()`](../vm_payload/include/vm_payload.h) 141 function.) 142- `onPayloadFinished()`: The VM payload has exited normally. The exit code of 143 the VM (the value returned by [`AVmPayload_main()`](../vm_payload/README.md)) 144 is supplied as a parameter. 145- `onError()`: The VM failed; something went wrong. An error code and 146 human-readable message are provided which may help diagnosing the problem. 147- `onStopped()`: The VM is no longer running. This is the final notification 148 from any VM run, whether or not it was successful. You can run the VM again 149 when you want to. A reason code indicating why the VM stopped is supplied as a 150 parameter. 151 152You can also query the status of a VM at any point by calling `getStatus()` on 153the `VirtualMachine` object. This will return one of the following values: 154- `STATUS_STOPPED`: The VM is not running - either it has not yet been started, 155 or it stopped after running. 156- `STATUS_RUNNING`: The VM is running. Your payload inside the VM may not be 157 running, since the VM may be in the process of starting or stopping. 158- `STATUS_DELETED`: The VM has been deleted, e.g. by calling the `delete()` 159 method on 160 [`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java). This 161 is irreversible - once a VM is in this state it will never leave it. 162 163Some methods on 164[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java) can 165only be called when the VM status is `STATUS_RUNNING` (e.g. `stop()`), and some 166can only be called when the it is `STATUS_STOPPED` (e.g. `run()`). 167 168## VM Identity and Secrets 169 170Every VM has a 32-byte secret unique to it, which is not available to the 171host. We refer to this as the VM identity. The secret, and thus the identity, 172doesn’t normally change if the same VM is stopped and started, even after a 173reboot. 174 175In Android 14 the secret is derived, using the [Open Profile for 176DICE](https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md), 177from: 178- A device-specific randomly generated value; 179- The complete system image; 180- A per-instance salt; 181- The code running in the VM, including the bootloader, kernel, Microdroid and 182 payload; 183- Significant VM configuration options, e.g. whether the VM is debuggable. 184 185Any change to any of these will mean a different secret is generated. So while 186an attacker could start a similar VM with maliciously altered code, that VM will 187not have access to the same secret. An attempt to start an existing VM instance 188which doesn't derive the same secret will fail. 189 190However, this also means that if the payload code changes - for example, your 191app is updated - then this also changes the identity. An existing VM instance 192will no longer be runnable, and you will have to delete it and create a new 193instance with a new secret. 194 195The payload code is not given direct access to the VM secret, but an API is 196provided to allow deterministically deriving further secrets from it, 197e.g. encryption or signing keys. See 198[`AVmPayload_getVmInstanceSecret()`](../vm_payload/include/vm_payload.h). 199 200Some VM configuration changes are allowed that don’t affect the identity - 201e.g. changing the number of CPUs or the amount of memory allocated to the 202VM. This can be done using the `setConfig()` method on 203[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java). 204 205Deleting a VM (using the `delete()` method on 206[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java)) 207and recreating it will generate a new salt, so the new VM will have a different 208secret, even if it is otherwise identical. 209 210## Communicating with a VM 211 212Once the VM payload has successfully started you will probably want to establish 213communication between it and your app. 214 215Only the app that started a VM can connect to it. The VM can accept connections 216from the app, but cannot initiate connections to other VMs or other processes in 217the host Android. 218 219### Vsock 220 221The simplest form of communication is using a socket running over the 222[vsock](https://man7.org/linux/man-pages/man7/vsock.7.html) protocol. 223 224We suggest that the VM payload should create a listening socket (using the 225standard socket API) and then trigger the `onPayloadReady()` callback; the app 226can then connect to the socket. This helps to avoid a race condition where the 227app tries to connect before the VM is listening, necessitating a retry 228mechanism. 229 230In the payload this might look like this: 231 232```C++ 233#include "vm_payload.h" 234 235extern "C" int AVmPayload_main() { 236 int fd = socket(AF_VSOCK, SOCK_STREAM, 0); 237 // bind, listen 238 AVmPayload_notifyPayloadReady(); 239 // accept, read/write, ... 240} 241``` 242 243And, in the app, like this: 244 245```Java 246void onPayloadReady(VirtualMachine vm) { 247 ParcelFileDescriptor pfd = vm.connectVsock(port); 248 // ... 249} 250``` 251 252Vsock is useful for simple communication, or transferring of bulk data. For a 253richer RPC style of communication we suggest using Binder. 254 255### Binder 256 257The use of AIDL interfaces between the VM and app is supported via Binder RPC, 258which transmits messages over an underlying vsock socket. 259 260Note that Binder RPC has some limitations compared to the kernel Binder used in 261Android - for example file descriptors can't be sent. It also isn't possible to 262send a kernel Binder interface over Binder RPC, or vice versa. 263 264There is a payload API to allow an AIDL interface to be served over a specific 265vsock port, and the VirtualMachine class provides a way to connect to the VM and 266retrieve an instance of the interface. 267 268The payload code to serve a hypothetical `IPayload` interface might look like 269this: 270 271```C++ 272class PayloadImpl : public BnPayload { ... }; 273 274 275extern "C" int AVmPayload_main() { 276 auto service = ndk::SharedRefBase::make<PayloadImpl>(); 277 auto callback = [](void*) { 278 AVmPayload_notifyPayloadReady(); 279 }; 280 AVmPayload_runVsockRpcServer(service->asBinder().get(), 281 port, callback, nullptr); 282} 283 284``` 285 286And then the app code to connect to it could look like this: 287 288```Java 289void onPayloadReady(VirtualMachine vm) { 290 IPayload payload = 291 Payload.Stub.asInterface(vm.connectToVsockServer(port)); 292 // ... 293} 294``` 295 296## Stopping a VM 297 298You can stop a VM abruptly by calling the `stop()` method on the 299[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java) 300instance. This is equivalent to turning off the power; the VM gets no 301opportunity to clean up at all. Any unwritten data might be lost. 302 303A better strategy might be to wait for the VM to exit cleanly (e.g. waiting for 304the `onStopped()` callback). 305 306Then you can arrange for your VM payload code to exit when it has finished its 307task (by returning from [`AVmPayload_main()`](../vm_payload/README.md), or 308calling `exit()`). Alternatively you could exit when you receive a request to do 309so from the app, e.g. via binder. 310 311When the VM payload does this you will receive an `onPayloadFinished()` 312callback, if you have installed a 313[`VirtualMachineCallback`](src/android/system/virtualmachine/VirtualMachineCallback.java), 314which includes the payload's exit code. 315 316Use of `stop()` should be reserved as a recovery mechanism - for example if the 317VM has not stopped within a reasonable time (a few seconds, say) after being 318requested to. 319 320The status of a VM will be `STATUS_STOPPED` if your `onStopped()` callback is 321invoked, or after a successful call to `stop()`. Note that your `onStopped()` 322will be called on the VM even if it ended as a result of a call to `stop()`. 323 324# Encrypted Storage 325 326When configuring a VM you can specify that it should have access to an encrypted 327storage filesystem of up to a specified size, using the 328`setEncryptedStorageBytes()` method on a 329[`VirtualMachineConfig`](src/android/system/virtualmachine/VirtualMachineConfig.java) 330builder. 331 332Inside the VM this storage is mounted at a path that can be retrieved via the 333[`AVmPayload_getEncryptedStoragePath()`](../vm_payload/include/vm_payload.h) 334function. The VM can create sub-directories and read and write files here. Any 335data written is persisted and should be available next time the VM is run. (An 336automatic sync is done when the payload exits normally.) 337 338Outside the VM the storage is persisted as a file in the app’s private data 339directory. The data is encrypted using a key derived from the VM secret, which 340is not made available outside the VM. 341 342So an attacker should not be able to decrypt the data; however, a sufficiently 343powerful attacker could delete it, wholly or partially roll it back to an 344earlier version, or modify it, corrupting the data. 345 346For more info see [README](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/java/framework/README.md) 347 348# Transferring a VM 349 350It is possible to make a copy of a VM instance. This can be used to transfer a 351VM from one app to another, which can be useful in some circumstances. 352 353This should only be done while the VM is stopped. The first step is to call 354`toDescriptor()` on the 355[`VirtualMachine`](src/android/system/virtualmachine/VirtualMachine.java) 356instance, which returns a 357[`VirtualMachineDescriptor`](src/android/system/virtualmachine/VirtualMachineDescriptor.java) 358object. This object internally contains open file descriptors to the files that 359hold the VM's state (its instance data, configuration, and encrypted storage). 360 361A `VirtualMachineDescriptor` is 362[`Parcelable`](https://developer.android.com/reference/android/os/Parcelable), 363so it can be passed to another app via a Binder call. Any app with a 364`VirtualMachineDescriptor` can pass it, along with a new VM name, to the 365`importFromDescriptor()` method on 366[`VirtualMachineManager`](src/android/system/virtualmachine/VirtualMachineManager.java). This 367is equivalent to calling `create()` with the same name and configuration, except 368that the new VM is the same instance as the original, with the same VM secret, 369and has access to a copy of the original's encrypted storage. 370 371Once the transfer has been completed it would be reasonable to delete the 372original VM, using the `delete()` method on `VirtualMachineManager`. 373 374 375 376 377 378