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