1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.system.virtualmachine;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresFeature;
26 import android.annotation.RequiresPermission;
27 import android.annotation.StringDef;
28 import android.annotation.SystemApi;
29 import android.annotation.TestApi;
30 import android.annotation.WorkerThread;
31 import android.content.Context;
32 import android.content.pm.PackageManager;
33 import android.os.RemoteException;
34 import android.sysprop.HypervisorProperties;
35 import android.system.virtualizationservice.IVirtualizationService;
36 import android.util.ArrayMap;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.system.virtualmachine.flags.Flags;
40 
41 import java.io.File;
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.lang.ref.WeakReference;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.Map;
49 
50 /**
51  * Manages {@linkplain VirtualMachine virtual machine} instances created by an app. Each instance is
52  * created from a {@linkplain VirtualMachineConfig configuration} that defines the shape of the VM
53  * (RAM, CPUs), the code to execute within it, etc.
54  *
55  * <p>Each virtual machine instance is named; the configuration and related state of each is
56  * persisted in the app's private data directory and an instance can be retrieved given the name.
57  * The name must be a valid directory name and must not contain '/'.
58  *
59  * <p>The app can then start, stop and otherwise interact with the VM.
60  *
61  * <p>An instance of {@link VirtualMachineManager} can be obtained by calling {@link
62  * Context#getSystemService(Class)}.
63  *
64  * @hide
65  */
66 @SystemApi
67 @RequiresFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK)
68 public class VirtualMachineManager {
69     /**
70      * A lock used to synchronize the creation of virtual machines. It protects {@link #mVmsByName},
71      * but is also held throughout VM creation / retrieval / deletion, to prevent these actions
72      * racing with each other.
73      */
74     private static final Object sCreateLock = new Object();
75 
76     @NonNull private final Context mContext;
77 
78     /** @hide */
VirtualMachineManager(@onNull Context context)79     public VirtualMachineManager(@NonNull Context context) {
80         mContext = requireNonNull(context);
81     }
82 
83     @GuardedBy("sCreateLock")
84     private final Map<String, WeakReference<VirtualMachine>> mVmsByName = new ArrayMap<>();
85 
86     /**
87      * Capabilities of the virtual machine implementation.
88      *
89      * @hide
90      */
91     @Retention(RetentionPolicy.SOURCE)
92     @IntDef(prefix = "CAPABILITY_", flag = true, value = {
93             CAPABILITY_PROTECTED_VM,
94             CAPABILITY_NON_PROTECTED_VM
95     })
96     public @interface Capability {}
97 
98     /**
99      * The implementation supports creating protected VMs, whose memory is inaccessible to the host
100      * OS.
101      *
102      * @see VirtualMachineConfig.Builder#setProtectedVm
103      */
104     public static final int CAPABILITY_PROTECTED_VM = 1;
105 
106     /**
107      * The implementation supports creating non-protected VMs, whose memory is accessible to the
108      * host OS.
109      *
110      * @see VirtualMachineConfig.Builder#setProtectedVm
111      */
112     public static final int CAPABILITY_NON_PROTECTED_VM = 2;
113 
114     /**
115      * Features provided by {@link VirtualMachineManager}.
116      *
117      * @hide
118      */
119     @Retention(RetentionPolicy.SOURCE)
120     @StringDef(
121             prefix = "FEATURE_",
122             value = {
123                 FEATURE_DICE_CHANGES,
124                 FEATURE_LLPVM_CHANGES,
125                 FEATURE_MULTI_TENANT,
126                 FEATURE_NETWORK,
127                 FEATURE_REMOTE_ATTESTATION,
128                 FEATURE_VENDOR_MODULES,
129             })
130     public @interface Features {}
131 
132     /**
133      * Feature to include new data in the VM DICE chain.
134      *
135      * @hide
136      */
137     @TestApi
138     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
139     public static final String FEATURE_DICE_CHANGES = IVirtualizationService.FEATURE_DICE_CHANGES;
140 
141     /**
142      * Feature to run payload as non-root user.
143      *
144      * @hide
145      */
146     @TestApi
147     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
148     public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
149 
150     /**
151      * Feature to allow network features in VM.
152      *
153      * @hide
154      */
155     @TestApi public static final String FEATURE_NETWORK = IVirtualizationService.FEATURE_NETWORK;
156 
157     /**
158      * Feature to allow remote attestation in Microdroid.
159      *
160      * @hide
161      */
162     @TestApi
163     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
164     public static final String FEATURE_REMOTE_ATTESTATION =
165             IVirtualizationService.FEATURE_REMOTE_ATTESTATION;
166 
167     /**
168      * Feature to allow vendor modules in Microdroid.
169      *
170      * @hide
171      */
172     @TestApi
173     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
174     public static final String FEATURE_VENDOR_MODULES =
175             IVirtualizationService.FEATURE_VENDOR_MODULES;
176 
177     /**
178      * Feature to enable Secretkeeper protected secrets in Microdroid based pVMs.
179      *
180      * @hide
181      */
182     @TestApi
183     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
184     public static final String FEATURE_LLPVM_CHANGES = IVirtualizationService.FEATURE_LLPVM_CHANGES;
185 
186     /**
187      * Returns a set of flags indicating what this implementation of virtualization is capable of.
188      *
189      * @see #CAPABILITY_PROTECTED_VM
190      * @see #CAPABILITY_NON_PROTECTED_VM
191      * @hide
192      */
193     @SystemApi
194     @Capability
getCapabilities()195     public int getCapabilities() {
196         @Capability int result = 0;
197         if (HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
198             result |= CAPABILITY_PROTECTED_VM;
199         }
200         if (HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
201             result |= CAPABILITY_NON_PROTECTED_VM;
202         }
203         return result;
204     }
205 
206     /**
207      * Creates a new {@link VirtualMachine} with the given name and config. Creating a virtual
208      * machine with the same name as an existing virtual machine is an error. The existing virtual
209      * machine has to be deleted before its name can be reused.
210      *
211      * <p>Each successful call to this method creates a new (and different) virtual machine even if
212      * the name and the config are the same as a deleted one. The new virtual machine will initially
213      * be stopped.
214      *
215      * <p>NOTE: This method may block and should not be called on the main thread.
216      *
217      * @throws VirtualMachineException if the VM cannot be created, or there is an existing VM with
218      *     the given name.
219      * @hide
220      */
221     @SystemApi
222     @NonNull
223     @WorkerThread
224     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
create(@onNull String name, @NonNull VirtualMachineConfig config)225     public VirtualMachine create(@NonNull String name, @NonNull VirtualMachineConfig config)
226             throws VirtualMachineException {
227         synchronized (sCreateLock) {
228             return createLocked(name, config);
229         }
230     }
231 
232     @NonNull
233     @GuardedBy("sCreateLock")
createLocked(@onNull String name, @NonNull VirtualMachineConfig config)234     private VirtualMachine createLocked(@NonNull String name, @NonNull VirtualMachineConfig config)
235             throws VirtualMachineException {
236         VirtualMachine vm = VirtualMachine.create(mContext, name, config);
237         mVmsByName.put(name, new WeakReference<>(vm));
238         return vm;
239     }
240 
241     /**
242      * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
243      * such virtual machine.
244      *
245      * <p>There is at most one {@code VirtualMachine} object corresponding to a given virtual
246      * machine instance. Multiple calls to get() passing the same name will get the same object
247      * returned, until the virtual machine is deleted (via {@link #delete}) and then recreated.
248      *
249      * <p>NOTE: This method may block and should not be called on the main thread.
250      *
251      * @see #getOrCreate
252      * @throws VirtualMachineException if the virtual machine exists but could not be successfully
253      *     retrieved. This can be resolved by calling {@link #delete} on the VM.
254      * @hide
255      */
256     @SystemApi
257     @WorkerThread
258     @Nullable
get(@onNull String name)259     public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
260         synchronized (sCreateLock) {
261             return getLocked(name);
262         }
263     }
264 
265     @Nullable
266     @GuardedBy("sCreateLock")
getLocked(@onNull String name)267     private VirtualMachine getLocked(@NonNull String name) throws VirtualMachineException {
268         VirtualMachine vm = getVmByName(name);
269         if (vm != null) return vm;
270 
271         vm = VirtualMachine.load(mContext, name);
272         if (vm != null) {
273             mVmsByName.put(name, new WeakReference<>(vm));
274         }
275         return vm;
276     }
277 
278     /**
279      * Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
280      * with the given name.
281      *
282      * <p>The new virtual machine will be in the same state as the descriptor indicates. The
283      * descriptor is automatically closed and cannot be used again.
284      *
285      * <p>NOTE: This method may block and should not be called on the main thread.
286      *
287      * @throws VirtualMachineException if the VM cannot be imported or the {@code
288      *     VirtualMachineDescriptor} has already been closed.
289      * @hide
290      */
291     @NonNull
292     @SystemApi
293     @WorkerThread
importFromDescriptor( @onNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)294     public VirtualMachine importFromDescriptor(
295             @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
296             throws VirtualMachineException {
297         VirtualMachine vm;
298         synchronized (sCreateLock) {
299             vm = VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
300             mVmsByName.put(name, new WeakReference<>(vm));
301         }
302         return vm;
303     }
304 
305     /**
306      * Returns an existing {@link VirtualMachine} if it exists, or create a new one. The config
307      * parameter is used only when a new virtual machine is created.
308      *
309      * <p>NOTE: This method may block and should not be called on the main thread.
310      *
311      * @throws VirtualMachineException if the virtual machine could not be created or retrieved.
312      * @hide
313      */
314     @SystemApi
315     @WorkerThread
316     @NonNull
getOrCreate(@onNull String name, @NonNull VirtualMachineConfig config)317     public VirtualMachine getOrCreate(@NonNull String name, @NonNull VirtualMachineConfig config)
318             throws VirtualMachineException {
319         synchronized (sCreateLock) {
320             VirtualMachine vm = getLocked(name);
321             if (vm != null) {
322                 return vm;
323             } else {
324                 return createLocked(name, config);
325             }
326         }
327     }
328 
329     /**
330      * Deletes an existing {@link VirtualMachine}. Deleting a virtual machine means deleting any
331      * persisted data associated with it including the per-VM secret. This is an irreversible
332      * action. A virtual machine once deleted can never be restored. A new virtual machine created
333      * with the same name is different from an already deleted virtual machine even if it has the
334      * same config.
335      *
336      * <p>NOTE: This method may block and should not be called on the main thread.
337      *
338      * @throws VirtualMachineException if the virtual machine does not exist, is not stopped, or
339      *     cannot be deleted.
340      * @hide
341      */
342     @SystemApi
343     @WorkerThread
delete(@onNull String name)344     public void delete(@NonNull String name) throws VirtualMachineException {
345         synchronized (sCreateLock) {
346             VirtualMachine vm = getVmByName(name);
347             if (vm == null) {
348                 VirtualMachine.vmInstanceCleanup(mContext, name);
349             } else {
350                 vm.delete(mContext, name);
351             }
352             mVmsByName.remove(name);
353         }
354     }
355 
356     @Nullable
357     @GuardedBy("sCreateLock")
getVmByName(@onNull String name)358     private VirtualMachine getVmByName(@NonNull String name) {
359         requireNonNull(name);
360         WeakReference<VirtualMachine> weakReference = mVmsByName.get(name);
361         if (weakReference != null) {
362             VirtualMachine vm = weakReference.get();
363             if (vm != null && vm.getStatus() != VirtualMachine.STATUS_DELETED) {
364                 return vm;
365             }
366         }
367         return null;
368     }
369 
370     private static final String JSON_SUFFIX = ".json";
371     private static final List<String> SUPPORTED_OS_LIST_FROM_CFG =
372             extractSupportedOSListFromConfig();
373 
isVendorModuleEnabled()374     private boolean isVendorModuleEnabled() {
375         return VirtualizationService.nativeIsVendorModulesFlagEnabled();
376     }
377 
extractSupportedOSListFromConfig()378     private static List<String> extractSupportedOSListFromConfig() {
379         List<String> supportedOsList = new ArrayList<>();
380         File directory = new File("/apex/com.android.virt/etc");
381         File[] files = directory.listFiles();
382         if (files != null) {
383             for (File file : files) {
384                 String fileName = file.getName();
385                 if (fileName.endsWith(JSON_SUFFIX)) {
386                     supportedOsList.add(
387                             fileName.substring(0, fileName.length() - JSON_SUFFIX.length()));
388                 }
389             }
390         }
391         return supportedOsList;
392     }
393 
394     /**
395      * Returns a list of supported OS names.
396      *
397      * @hide
398      */
399     @TestApi
400     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
401     @NonNull
getSupportedOSList()402     public List<String> getSupportedOSList() throws VirtualMachineException {
403         if (isVendorModuleEnabled()) {
404             return SUPPORTED_OS_LIST_FROM_CFG;
405         } else {
406             return Arrays.asList("microdroid");
407         }
408     }
409 
410     /**
411      * Returns {@code true} if given {@code featureName} is enabled.
412      *
413      * @hide
414      */
415     @TestApi
416     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
417     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
isFeatureEnabled(@eatures String featureName)418     public boolean isFeatureEnabled(@Features String featureName) throws VirtualMachineException {
419         synchronized (sCreateLock) {
420             VirtualizationService service = VirtualizationService.getInstance();
421             try {
422                 return service.getBinder().isFeatureEnabled(featureName);
423             } catch (RemoteException e) {
424                 throw e.rethrowAsRuntimeException();
425             }
426         }
427     }
428 
429     /**
430      * Returns {@code true} if the pVM remote attestation feature is supported. Remote attestation
431      * allows a protected VM to attest its authenticity to a remote server.
432      *
433      * @hide
434      */
435     @TestApi
436     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
437     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
isRemoteAttestationSupported()438     public boolean isRemoteAttestationSupported() throws VirtualMachineException {
439         synchronized (sCreateLock) {
440             VirtualizationService service = VirtualizationService.getInstance();
441             try {
442                 return service.getBinder().isRemoteAttestationSupported();
443             } catch (RemoteException e) {
444                 throw e.rethrowAsRuntimeException();
445             }
446         }
447     }
448 
449     /**
450      * Returns {@code true} if Updatable VM feature is supported by AVF. Updatable VM allow secrets
451      * and data to be accessible even after updates of boot images and apks. For more info see
452      * packages/modules/Virtualization/docs/updatable_vm.md
453      *
454      * @hide
455      */
456     @TestApi
457     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
458     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
isUpdatableVmSupported()459     public boolean isUpdatableVmSupported() throws VirtualMachineException {
460         synchronized (sCreateLock) {
461             VirtualizationService service = VirtualizationService.getInstance();
462             try {
463                 return service.getBinder().isUpdatableVmSupported();
464             } catch (RemoteException e) {
465                 throw e.rethrowAsRuntimeException();
466             }
467         }
468     }
469 }
470