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 package com.android.microdroid.test;
17 
18 import static android.system.virtualmachine.VirtualMachine.STATUS_DELETED;
19 import static android.system.virtualmachine.VirtualMachine.STATUS_RUNNING;
20 import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
21 import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
22 import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
23 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
24 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
25 import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM;
26 import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_PROTECTED_VM;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 import static com.google.common.truth.Truth.assertWithMessage;
30 import static com.google.common.truth.TruthJUnit.assume;
31 
32 import static org.junit.Assert.assertThrows;
33 import static org.junit.Assert.assertTrue;
34 import static org.junit.Assume.assumeFalse;
35 import static org.junit.Assume.assumeTrue;
36 
37 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
38 import static java.util.stream.Collectors.toList;
39 
40 import android.app.ActivityManager;
41 import android.app.Instrumentation;
42 import android.app.UiAutomation;
43 import android.content.ComponentName;
44 import android.content.Context;
45 import android.content.ContextWrapper;
46 import android.content.Intent;
47 import android.content.ServiceConnection;
48 import android.os.Build;
49 import android.os.IBinder;
50 import android.os.Parcel;
51 import android.os.ParcelFileDescriptor;
52 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
53 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
54 import android.os.SystemProperties;
55 import android.system.OsConstants;
56 import android.system.virtualmachine.VirtualMachine;
57 import android.system.virtualmachine.VirtualMachineCallback;
58 import android.system.virtualmachine.VirtualMachineConfig;
59 import android.system.virtualmachine.VirtualMachineDescriptor;
60 import android.system.virtualmachine.VirtualMachineException;
61 import android.system.virtualmachine.VirtualMachineManager;
62 
63 import androidx.test.platform.app.InstrumentationRegistry;
64 
65 import com.android.compatibility.common.util.CddTest;
66 import com.android.compatibility.common.util.VsrTest;
67 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
68 import com.android.microdroid.test.vmshare.IVmShareTestService;
69 import com.android.microdroid.testservice.IAppCallback;
70 import com.android.microdroid.testservice.ITestService;
71 import com.android.microdroid.testservice.IVmCallback;
72 import com.android.virt.vm_attestation.testservice.IAttestationService.AttestationStatus;
73 import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
74 import com.android.virt.vm_attestation.util.X509Utils;
75 
76 import co.nstant.in.cbor.CborDecoder;
77 import co.nstant.in.cbor.model.Array;
78 import co.nstant.in.cbor.model.DataItem;
79 import co.nstant.in.cbor.model.MajorType;
80 
81 import com.google.common.base.Strings;
82 import com.google.common.truth.BooleanSubject;
83 
84 import org.junit.After;
85 import org.junit.Before;
86 import org.junit.Ignore;
87 import org.junit.Rule;
88 import org.junit.Test;
89 import org.junit.function.ThrowingRunnable;
90 import org.junit.rules.Timeout;
91 import org.junit.runner.RunWith;
92 import org.junit.runners.Parameterized;
93 
94 import java.io.BufferedReader;
95 import java.io.ByteArrayInputStream;
96 import java.io.File;
97 import java.io.FileInputStream;
98 import java.io.IOException;
99 import java.io.InputStream;
100 import java.io.InputStreamReader;
101 import java.io.OutputStream;
102 import java.io.OutputStreamWriter;
103 import java.io.RandomAccessFile;
104 import java.io.Writer;
105 import java.nio.file.Files;
106 import java.nio.file.Path;
107 import java.nio.file.Paths;
108 import java.security.cert.X509Certificate;
109 import java.time.LocalDateTime;
110 import java.time.format.DateTimeFormatter;
111 import java.util.ArrayList;
112 import java.util.Arrays;
113 import java.util.Collection;
114 import java.util.List;
115 import java.util.OptionalLong;
116 import java.util.UUID;
117 import java.util.concurrent.CompletableFuture;
118 import java.util.concurrent.CountDownLatch;
119 import java.util.concurrent.TimeUnit;
120 import java.util.concurrent.atomic.AtomicReference;
121 import java.util.stream.Stream;
122 
123 @RunWith(Parameterized.class)
124 public class MicrodroidTests extends MicrodroidDeviceTestBase {
125     private static final String TAG = "MicrodroidTests";
126     private static final String TEST_APP_PACKAGE_NAME = "com.android.microdroid.test";
127     private static final String VM_ATTESTATION_PAYLOAD_PATH = "libvm_attestation_test_payload.so";
128     private static final String VM_ATTESTATION_MESSAGE = "Hello RKP from AVF!";
129     private static final int ENCRYPTED_STORAGE_BYTES = 4_000_000;
130 
131     @Rule public Timeout globalTimeout = Timeout.seconds(300);
132 
133     @Parameterized.Parameters(name = "protectedVm={0},os={1}")
params()134     public static Collection<Object[]> params() {
135         List<Object[]> ret = new ArrayList<>();
136         // TODO(b/302465542): run only the latest GKI on presubmit to reduce running time
137         for (String os : SUPPORTED_OSES) {
138             ret.add(new Object[] {true /* protectedVm */, os});
139             ret.add(new Object[] {false /* protectedVm */, os});
140         }
141         return ret;
142     }
143 
144     @Parameterized.Parameter(0)
145     public boolean mProtectedVm;
146 
147     @Parameterized.Parameter(1)
148     public String mOs;
149 
150     @Before
setup()151     public void setup() {
152         prepareTestSetup(mProtectedVm, mOs);
153         if (mOs != "microdroid") {
154             // Using a non-default VM always needs the custom permission.
155             grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
156         } else {
157             // USE_CUSTOM_VIRTUAL_MACHINE permission has protection level signature|development,
158             // meaning that it will be automatically granted when test apk is installed.
159             // But most callers shouldn't need this permission, so by default we run tests with it
160             // revoked.
161             // Tests that rely on the state of the permission should explicitly grant or revoke it.
162             revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
163         }
164     }
165 
166     @After
tearDown()167     public void tearDown() {
168         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
169     }
170 
171     private static final long ONE_MEBI = 1024 * 1024;
172 
173     private static final long MIN_MEM_ARM64 = 170 * ONE_MEBI;
174     private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
175     private static final String EXAMPLE_STRING = "Literally any string!! :)";
176 
177     private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
178 
createAndConnectToVmHelper(int cpuTopology)179     private void createAndConnectToVmHelper(int cpuTopology) throws Exception {
180         assumeSupportedDevice();
181 
182         VirtualMachineConfig config =
183                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
184                         .setMemoryBytes(minMemoryRequired())
185                         .setDebugLevel(DEBUG_LEVEL_FULL)
186                         .setCpuTopology(cpuTopology)
187                         .build();
188         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
189 
190         TestResults testResults =
191                 runVmTestService(
192                         TAG,
193                         vm,
194                         (ts, tr) -> {
195                             tr.mAddInteger = ts.addInteger(123, 456);
196                             tr.mAppRunProp = ts.readProperty("debug.microdroid.app.run");
197                             tr.mSublibRunProp = ts.readProperty("debug.microdroid.app.sublib.run");
198                             tr.mApkContentsPath = ts.getApkContentsPath();
199                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
200                             tr.mInstanceSecret = ts.insecurelyExposeVmInstanceSecret();
201                         });
202         testResults.assertNoException();
203         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
204         assertThat(testResults.mAppRunProp).isEqualTo("true");
205         assertThat(testResults.mSublibRunProp).isEqualTo("true");
206         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
207         assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
208         assertThat(testResults.mInstanceSecret).hasLength(32);
209     }
210 
211     @Test
212     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndConnectToVm()213     public void createAndConnectToVm() throws Exception {
214         createAndConnectToVmHelper(CPU_TOPOLOGY_ONE_CPU);
215     }
216 
217     @Test
218     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndConnectToVm_HostCpuTopology()219     public void createAndConnectToVm_HostCpuTopology() throws Exception {
220         createAndConnectToVmHelper(CPU_TOPOLOGY_MATCH_HOST);
221     }
222 
223     @Test
224     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
225     @VsrTest(requirements = {"VSR-7.1-001.006"})
vmAttestationWhenRemoteAttestationIsNotSupported()226     public void vmAttestationWhenRemoteAttestationIsNotSupported() throws Exception {
227         // pVM remote attestation is only supported on protected VMs.
228         assumeProtectedVM();
229         assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
230         assume().withMessage(
231                         "This test does not apply to a device that supports Remote Attestation")
232                 .that(getVirtualMachineManager().isRemoteAttestationSupported())
233                 .isFalse();
234         VirtualMachineConfig config =
235                 newVmConfigBuilderWithPayloadBinary(VM_ATTESTATION_PAYLOAD_PATH)
236                         .setProtectedVm(mProtectedVm)
237                         .setDebugLevel(DEBUG_LEVEL_FULL)
238                         .build();
239         VirtualMachine vm =
240                 forceCreateNewVirtualMachine("cts_attestation_with_rkpd_unsupported", config);
241         byte[] challenge = new byte[32];
242         Arrays.fill(challenge, (byte) 0xcc);
243 
244         // Act.
245         SigningResult signingResult =
246                 runVmAttestationService(TAG, vm, challenge, VM_ATTESTATION_MESSAGE.getBytes());
247 
248         // Assert.
249         assertThat(signingResult.status).isEqualTo(AttestationStatus.ERROR_UNSUPPORTED);
250     }
251 
252     @Test
253     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
254     @VsrTest(requirements = {"VSR-7.1-001.006"})
vmAttestationWithVendorPartitionWhenSupported()255     public void vmAttestationWithVendorPartitionWhenSupported() throws Exception {
256         // pVM remote attestation is only supported on protected VMs.
257         assumeProtectedVM();
258         assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
259         assume().withMessage("Test needs Remote Attestation support")
260                 .that(getVirtualMachineManager().isRemoteAttestationSupported())
261                 .isTrue();
262         File vendorDiskImage = new File("/vendor/etc/avf/microdroid/microdroid_vendor.img");
263         assumeTrue("Microdroid vendor image doesn't exist, skip", vendorDiskImage.exists());
264         VirtualMachineConfig config =
265                 buildVmConfigWithVendor(vendorDiskImage, VM_ATTESTATION_PAYLOAD_PATH);
266         VirtualMachine vm =
267                 forceCreateNewVirtualMachine("cts_attestation_with_vendor_module", config);
268         checkVmAttestationWithValidChallenge(vm);
269     }
270 
271     @Test
272     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
273     @VsrTest(requirements = {"VSR-7.1-001.006"})
vmAttestationWhenRemoteAttestationIsSupported()274     public void vmAttestationWhenRemoteAttestationIsSupported() throws Exception {
275         // pVM remote attestation is only supported on protected VMs.
276         assumeProtectedVM();
277         assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
278         assume().withMessage("Test needs Remote Attestation support")
279                 .that(getVirtualMachineManager().isRemoteAttestationSupported())
280                 .isTrue();
281         VirtualMachineConfig config =
282                 newVmConfigBuilderWithPayloadBinary(VM_ATTESTATION_PAYLOAD_PATH)
283                         .setProtectedVm(mProtectedVm)
284                         .setDebugLevel(DEBUG_LEVEL_FULL)
285                         .build();
286         VirtualMachine vm =
287                 forceCreateNewVirtualMachine("cts_attestation_with_rkpd_supported", config);
288 
289         // Check with an invalid challenge.
290         byte[] invalidChallenge = new byte[65];
291         Arrays.fill(invalidChallenge, (byte) 0xbb);
292         SigningResult signingResultInvalidChallenge =
293                 runVmAttestationService(
294                         TAG, vm, invalidChallenge, VM_ATTESTATION_MESSAGE.getBytes());
295         assertThat(signingResultInvalidChallenge.status)
296                 .isEqualTo(AttestationStatus.ERROR_INVALID_CHALLENGE);
297 
298         // Check with a valid challenge.
299         checkVmAttestationWithValidChallenge(vm);
300     }
301 
checkVmAttestationWithValidChallenge(VirtualMachine vm)302     private void checkVmAttestationWithValidChallenge(VirtualMachine vm) throws Exception {
303         byte[] challenge = new byte[32];
304         Arrays.fill(challenge, (byte) 0xac);
305         SigningResult signingResult =
306                 runVmAttestationService(TAG, vm, challenge, VM_ATTESTATION_MESSAGE.getBytes());
307         assertWithMessage(
308                         "VM attestation should either succeed or fail when the network is unstable")
309                 .that(signingResult.status)
310                 .isAnyOf(AttestationStatus.OK, AttestationStatus.ERROR_ATTESTATION_FAILED);
311         if (signingResult.status == AttestationStatus.OK) {
312             X509Certificate[] certs =
313                     X509Utils.validateAndParseX509CertChain(signingResult.certificateChain);
314             X509Utils.verifyAvfRelatedCerts(certs, challenge, TEST_APP_PACKAGE_NAME);
315             X509Utils.verifySignature(
316                     certs[0], VM_ATTESTATION_MESSAGE.getBytes(), signingResult.signature);
317         }
318     }
319 
320     @Test
321     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndRunNoDebugVm()322     public void createAndRunNoDebugVm() throws Exception {
323         assumeSupportedDevice();
324 
325         // For most of our tests we use a debug VM so failures can be diagnosed.
326         // But we do need non-debug VMs to work, so run one.
327         VirtualMachineConfig config =
328                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
329                         .setMemoryBytes(minMemoryRequired())
330                         .setDebugLevel(DEBUG_LEVEL_NONE)
331                         .setVmOutputCaptured(false)
332                         .build();
333         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
334 
335         TestResults testResults =
336                 runVmTestService(TAG, vm, (ts, tr) -> tr.mAddInteger = ts.addInteger(37, 73));
337         testResults.assertNoException();
338         assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
339     }
340 
341     @Test
342     @CddTest(requirements = {"9.17/C-1-1"})
autoCloseVm()343     public void autoCloseVm() throws Exception {
344         assumeSupportedDevice();
345 
346         VirtualMachineConfig config =
347                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
348                         .setMemoryBytes(minMemoryRequired())
349                         .setDebugLevel(DEBUG_LEVEL_FULL)
350                         .build();
351 
352         try (VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config)) {
353             assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
354             // close() implicitly called on stopped VM.
355         }
356 
357         try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
358             vm.run();
359             assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
360             // close() implicitly called on running VM.
361         }
362 
363         try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
364             assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
365             getVirtualMachineManager().delete("test_vm");
366             assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
367             // close() implicitly called on deleted VM.
368         }
369     }
370 
371     @Test
372     @CddTest(requirements = {"9.17/C-1-1"})
autoCloseVmDescriptor()373     public void autoCloseVmDescriptor() throws Exception {
374         VirtualMachineConfig config =
375                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
376                         .setDebugLevel(DEBUG_LEVEL_FULL)
377                         .build();
378         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
379         VirtualMachineDescriptor descriptor = vm.toDescriptor();
380 
381         Parcel parcel = Parcel.obtain();
382         try (descriptor) {
383             // It should be ok to use at this point
384             descriptor.writeToParcel(parcel, 0);
385         }
386 
387         // But not now - it's been closed.
388         assertThrows(IllegalStateException.class, () -> descriptor.writeToParcel(parcel, 0));
389         assertThrows(
390                 IllegalStateException.class,
391                 () -> getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor));
392 
393         // Closing again is fine.
394         descriptor.close();
395 
396         // Tidy up
397         parcel.recycle();
398     }
399 
400     @Test
401     @CddTest(requirements = {"9.17/C-1-1"})
vmDescriptorClosedOnImport()402     public void vmDescriptorClosedOnImport() throws Exception {
403         VirtualMachineConfig config =
404                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
405                         .setDebugLevel(DEBUG_LEVEL_FULL)
406                         .build();
407         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
408         VirtualMachineDescriptor descriptor = vm.toDescriptor();
409 
410         getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor);
411         try {
412             // Descriptor has been implicitly closed
413             assertThrows(
414                     IllegalStateException.class,
415                     () ->
416                             getVirtualMachineManager()
417                                     .importFromDescriptor("imported_vm2", descriptor));
418         } finally {
419             getVirtualMachineManager().delete("imported_vm");
420         }
421     }
422 
423     @Test
424     @CddTest(requirements = {"9.17/C-1-1"})
vmLifecycleChecks()425     public void vmLifecycleChecks() throws Exception {
426         assumeSupportedDevice();
427 
428         VirtualMachineConfig config =
429                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
430                         .setMemoryBytes(minMemoryRequired())
431                         .setDebugLevel(DEBUG_LEVEL_FULL)
432                         .build();
433 
434         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
435         assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
436 
437         // These methods require a running VM
438         assertThrowsVmExceptionContaining(
439                 () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "not in running state");
440         assertThrowsVmExceptionContaining(
441                 () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT),
442                 "not in running state");
443 
444         vm.run();
445         assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
446 
447         // These methods require a stopped VM
448         assertThrowsVmExceptionContaining(() -> vm.run(), "not in stopped state");
449         assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "not in stopped state");
450         assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "not in stopped state");
451         assertThrowsVmExceptionContaining(
452                 () -> getVirtualMachineManager().delete("test_vm"), "not in stopped state");
453 
454         vm.stop();
455         getVirtualMachineManager().delete("test_vm");
456         assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
457 
458         // None of these should work for a deleted VM
459         assertThrowsVmExceptionContaining(
460                 () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "deleted");
461         assertThrowsVmExceptionContaining(
462                 () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT), "deleted");
463         assertThrowsVmExceptionContaining(() -> vm.run(), "deleted");
464         assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "deleted");
465         assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "deleted");
466         // This is indistinguishable from the VM having never existed, so the message
467         // is non-specific.
468         assertThrowsVmException(() -> getVirtualMachineManager().delete("test_vm"));
469     }
470 
471     @Test
472     @CddTest(requirements = {"9.17/C-1-1"})
connectVsock()473     public void connectVsock() throws Exception {
474         assumeSupportedDevice();
475 
476         VirtualMachineConfig config =
477                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
478                         .setMemoryBytes(minMemoryRequired())
479                         .setDebugLevel(DEBUG_LEVEL_FULL)
480                         .build();
481         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_vsock", config);
482 
483         AtomicReference<String> response = new AtomicReference<>();
484         String request = "Look not into the abyss";
485 
486         TestResults testResults =
487                 runVmTestService(
488                         TAG,
489                         vm,
490                         (service, results) -> {
491                             service.runEchoReverseServer();
492 
493                             ParcelFileDescriptor pfd =
494                                     vm.connectVsock(ITestService.ECHO_REVERSE_PORT);
495                             try (InputStream input = new AutoCloseInputStream(pfd);
496                                     OutputStream output = new AutoCloseOutputStream(pfd)) {
497                                 BufferedReader reader =
498                                         new BufferedReader(new InputStreamReader(input));
499                                 Writer writer = new OutputStreamWriter(output);
500                                 writer.write(request + "\n");
501                                 writer.flush();
502                                 response.set(reader.readLine());
503                             }
504                         });
505         testResults.assertNoException();
506         assertThat(response.get()).isEqualTo(new StringBuilder(request).reverse().toString());
507     }
508 
509     @Test
510     @CddTest(requirements = {"9.17/C-1-1"})
binderCallbacksWork()511     public void binderCallbacksWork() throws Exception {
512         assumeSupportedDevice();
513 
514         VirtualMachineConfig config =
515                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
516                         .setMemoryBytes(minMemoryRequired())
517                         .setDebugLevel(DEBUG_LEVEL_FULL)
518                         .build();
519         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
520 
521         String request = "Hello";
522         CompletableFuture<String> response = new CompletableFuture<>();
523 
524         IAppCallback appCallback =
525                 new IAppCallback.Stub() {
526                     @Override
527                     public void setVmCallback(IVmCallback vmCallback) {
528                         // Do this on a separate thread to simulate an asynchronous trigger,
529                         // and to make sure it doesn't happen in the context of an inbound binder
530                         // call.
531                         new Thread() {
532                             @Override
533                             public void run() {
534                                 try {
535                                     vmCallback.echoMessage(request);
536                                 } catch (Exception e) {
537                                     response.completeExceptionally(e);
538                                 }
539                             }
540                         }.start();
541                     }
542 
543                     @Override
544                     public void onEchoRequestReceived(String message) {
545                         response.complete(message);
546                     }
547                 };
548 
549         TestResults testResults =
550                 runVmTestService(
551                         TAG,
552                         vm,
553                         (service, results) -> {
554                             service.requestCallback(appCallback);
555                             response.get(10, TimeUnit.SECONDS);
556                         });
557         testResults.assertNoException();
558         assertThat(response.getNow("no response")).isEqualTo("Received: " + request);
559     }
560 
561     @Test
562     @CddTest(requirements = {"9.17/C-1-1"})
vmConfigGetAndSetTests()563     public void vmConfigGetAndSetTests() {
564         // Minimal has as little as specified as possible; everything that can be is defaulted.
565         VirtualMachineConfig.Builder minimalBuilder =
566                 new VirtualMachineConfig.Builder(getContext())
567                         .setPayloadConfigPath("config/path")
568                         .setProtectedVm(isProtectedVm());
569         VirtualMachineConfig minimal = minimalBuilder.build();
570 
571         assertThat(minimal.getApkPath()).isNull();
572         assertThat(minimal.getExtraApks()).isEmpty();
573         assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
574         assertThat(minimal.getMemoryBytes()).isEqualTo(0);
575         assertThat(minimal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_ONE_CPU);
576         assertThat(minimal.getPayloadBinaryName()).isNull();
577         assertThat(minimal.getPayloadConfigPath()).isEqualTo("config/path");
578         assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
579         assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
580         assertThat(minimal.getEncryptedStorageBytes()).isEqualTo(0);
581         assertThat(minimal.isVmOutputCaptured()).isFalse();
582         assertThat(minimal.getOs()).isEqualTo("microdroid");
583 
584         // Maximal has everything that can be set to some non-default value. (And has different
585         // values than minimal for the required fields.)
586         VirtualMachineConfig.Builder maximalBuilder =
587                 new VirtualMachineConfig.Builder(getContext())
588                         .setProtectedVm(mProtectedVm)
589                         .setPayloadBinaryName("binary.so")
590                         .setApkPath("/apk/path")
591                         .addExtraApk("package.name1")
592                         .addExtraApk("package.name2")
593                         .setDebugLevel(DEBUG_LEVEL_FULL)
594                         .setMemoryBytes(42)
595                         .setCpuTopology(CPU_TOPOLOGY_MATCH_HOST)
596                         .setEncryptedStorageBytes(1_000_000)
597                         .setVmOutputCaptured(true)
598                         .setOs("microdroid_gki-android14-6.1");
599         VirtualMachineConfig maximal = maximalBuilder.build();
600 
601         assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
602         assertThat(maximal.getExtraApks())
603                 .containsExactly("package.name1", "package.name2")
604                 .inOrder();
605         assertThat(maximal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_FULL);
606         assertThat(maximal.getMemoryBytes()).isEqualTo(42);
607         assertThat(maximal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_MATCH_HOST);
608         assertThat(maximal.getPayloadBinaryName()).isEqualTo("binary.so");
609         assertThat(maximal.getPayloadConfigPath()).isNull();
610         assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
611         assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
612         assertThat(maximal.getEncryptedStorageBytes()).isEqualTo(1_000_000);
613         assertThat(maximal.isVmOutputCaptured()).isTrue();
614         assertThat(maximal.getOs()).isEqualTo("microdroid_gki-android14-6.1");
615 
616         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
617         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
618         assertThat(maximal.isCompatibleWith(maximal)).isTrue();
619     }
620 
621     @Test
622     @CddTest(requirements = {"9.17/C-1-1"})
vmConfigBuilderValidationTests()623     public void vmConfigBuilderValidationTests() {
624         VirtualMachineConfig.Builder builder =
625                 new VirtualMachineConfig.Builder(getContext()).setProtectedVm(mProtectedVm);
626 
627         // All your null are belong to me.
628         assertThrows(NullPointerException.class, () -> new VirtualMachineConfig.Builder(null));
629         assertThrows(NullPointerException.class, () -> builder.setApkPath(null));
630         assertThrows(NullPointerException.class, () -> builder.addExtraApk(null));
631         assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
632         assertThrows(NullPointerException.class, () -> builder.setPayloadBinaryName(null));
633         assertThrows(NullPointerException.class, () -> builder.setVendorDiskImage(null));
634         assertThrows(NullPointerException.class, () -> builder.setOs(null));
635 
636         // Individual property checks.
637         assertThrows(
638                 IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
639         assertThrows(
640                 IllegalArgumentException.class, () -> builder.setPayloadBinaryName("dir/file.so"));
641         assertThrows(IllegalArgumentException.class, () -> builder.setDebugLevel(-1));
642         assertThrows(IllegalArgumentException.class, () -> builder.setMemoryBytes(0));
643         assertThrows(IllegalArgumentException.class, () -> builder.setCpuTopology(-1));
644         assertThrows(IllegalArgumentException.class, () -> builder.setEncryptedStorageBytes(0));
645 
646         // Consistency checks enforced at build time.
647         Exception e;
648         e = assertThrows(IllegalStateException.class, () -> builder.build());
649         assertThat(e).hasMessageThat().contains("setPayloadBinaryName must be called");
650 
651         VirtualMachineConfig.Builder protectedNotSet =
652                 new VirtualMachineConfig.Builder(getContext()).setPayloadBinaryName("binary.so");
653         e = assertThrows(IllegalStateException.class, () -> protectedNotSet.build());
654         assertThat(e).hasMessageThat().contains("setProtectedVm must be called");
655 
656         VirtualMachineConfig.Builder captureOutputOnNonDebuggable =
657                 newVmConfigBuilderWithPayloadBinary("binary.so")
658                         .setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_NONE)
659                         .setVmOutputCaptured(true);
660         e = assertThrows(IllegalStateException.class, () -> captureOutputOnNonDebuggable.build());
661         assertThat(e).hasMessageThat().contains("debug level must be FULL to capture output");
662 
663         VirtualMachineConfig.Builder captureInputOnNonDebuggable =
664                 newVmConfigBuilderWithPayloadBinary("binary.so")
665                         .setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_NONE)
666                         .setVmConsoleInputSupported(true);
667         e = assertThrows(IllegalStateException.class, () -> captureInputOnNonDebuggable.build());
668         assertThat(e).hasMessageThat().contains("debug level must be FULL to use console input");
669     }
670 
671     @Test
672     @CddTest(requirements = {"9.17/C-1-1"})
compatibleConfigTests()673     public void compatibleConfigTests() {
674         VirtualMachineConfig baseline = newBaselineBuilder().build();
675 
676         // A config must be compatible with itself
677         assertConfigCompatible(baseline, newBaselineBuilder()).isTrue();
678 
679         // Changes that must always be compatible
680         assertConfigCompatible(baseline, newBaselineBuilder().setMemoryBytes(99)).isTrue();
681         assertConfigCompatible(
682                         baseline, newBaselineBuilder().setCpuTopology(CPU_TOPOLOGY_MATCH_HOST))
683                 .isTrue();
684 
685         // Changes that must be incompatible, since they must change the VM identity.
686         assertConfigCompatible(baseline, newBaselineBuilder().addExtraApk("foo")).isFalse();
687         assertConfigCompatible(baseline, newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL))
688                 .isFalse();
689         assertConfigCompatible(baseline, newBaselineBuilder().setPayloadBinaryName("different"))
690                 .isFalse();
691         assertConfigCompatible(
692                         baseline, newBaselineBuilder().setVendorDiskImage(new File("/foo/bar")))
693                 .isFalse();
694         int capabilities = getVirtualMachineManager().getCapabilities();
695         if ((capabilities & CAPABILITY_PROTECTED_VM) != 0
696                 && (capabilities & CAPABILITY_NON_PROTECTED_VM) != 0) {
697             assertConfigCompatible(baseline, newBaselineBuilder().setProtectedVm(!isProtectedVm()))
698                     .isFalse();
699         }
700 
701         // Changes that were incompatible but are currently compatible, but not guaranteed to be
702         // so in the API spec.
703         assertConfigCompatible(baseline, newBaselineBuilder().setApkPath("/different")).isTrue();
704 
705         // Changes that are currently incompatible for ease of implementation, but this might change
706         // in the future.
707         assertConfigCompatible(baseline, newBaselineBuilder().setEncryptedStorageBytes(100_000))
708                 .isFalse();
709 
710         VirtualMachineConfig.Builder debuggableBuilder =
711                 newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL);
712         VirtualMachineConfig debuggable = debuggableBuilder.build();
713         assertConfigCompatible(debuggable, debuggableBuilder.setVmOutputCaptured(true)).isFalse();
714         assertConfigCompatible(debuggable, debuggableBuilder.setVmOutputCaptured(false)).isTrue();
715         assertConfigCompatible(debuggable, debuggableBuilder.setVmConsoleInputSupported(true))
716                 .isFalse();
717 
718         VirtualMachineConfig currentContextConfig =
719                 new VirtualMachineConfig.Builder(getContext())
720                         .setProtectedVm(isProtectedVm())
721                         .setPayloadBinaryName("binary.so")
722                         .build();
723 
724         // packageName is not directly exposed by the config, so we have to be a bit creative
725         // to modify it.
726         Context otherContext =
727                 new ContextWrapper(getContext()) {
728                     @Override
729                     public String getPackageName() {
730                         return "other.package.name";
731                     }
732                 };
733         VirtualMachineConfig.Builder otherContextBuilder =
734                 new VirtualMachineConfig.Builder(otherContext)
735                         .setProtectedVm(isProtectedVm())
736                         .setPayloadBinaryName("binary.so");
737         assertConfigCompatible(currentContextConfig, otherContextBuilder).isFalse();
738 
739         VirtualMachineConfig microdroidOsConfig = newBaselineBuilder().setOs("microdroid").build();
740         VirtualMachineConfig.Builder otherOsBuilder =
741                 newBaselineBuilder().setOs("microdroid_gki-android14-6.1");
742         assertConfigCompatible(microdroidOsConfig, otherOsBuilder).isFalse();
743     }
744 
newBaselineBuilder()745     private VirtualMachineConfig.Builder newBaselineBuilder() {
746         return newVmConfigBuilderWithPayloadBinary("binary.so").setApkPath("/apk/path");
747     }
748 
assertConfigCompatible( VirtualMachineConfig baseline, VirtualMachineConfig.Builder builder)749     private BooleanSubject assertConfigCompatible(
750             VirtualMachineConfig baseline, VirtualMachineConfig.Builder builder) {
751         return assertThat(builder.build().isCompatibleWith(baseline));
752     }
753 
754     @Test
755     @CddTest(requirements = {"9.17/C-1-1"})
vmUnitTests()756     public void vmUnitTests() throws Exception {
757         VirtualMachineConfig.Builder builder = newVmConfigBuilderWithPayloadBinary("binary.so");
758         VirtualMachineConfig config = builder.build();
759         VirtualMachine vm = forceCreateNewVirtualMachine("vm_name", config);
760 
761         assertThat(vm.getName()).isEqualTo("vm_name");
762         assertThat(vm.getConfig().getPayloadBinaryName()).isEqualTo("binary.so");
763         assertThat(vm.getConfig().getMemoryBytes()).isEqualTo(0);
764 
765         VirtualMachineConfig compatibleConfig = builder.setMemoryBytes(42).build();
766         vm.setConfig(compatibleConfig);
767 
768         assertThat(vm.getName()).isEqualTo("vm_name");
769         assertThat(vm.getConfig().getPayloadBinaryName()).isEqualTo("binary.so");
770         assertThat(vm.getConfig().getMemoryBytes()).isEqualTo(42);
771 
772         assertThat(getVirtualMachineManager().get("vm_name")).isSameInstanceAs(vm);
773     }
774 
775     @Test
776     @CddTest(requirements = {"9.17/C-1-1"})
testAvfRequiresUpdatableApex()777     public void testAvfRequiresUpdatableApex() throws Exception {
778         assertWithMessage("Devices that support AVF must also support updatable APEX")
779                 .that(SystemProperties.getBoolean("ro.apex.updatable", false))
780                 .isTrue();
781     }
782 
783     @Test
784     @CddTest(requirements = {"9.17/C-1-1"})
vmmGetAndCreate()785     public void vmmGetAndCreate() throws Exception {
786         assumeSupportedDevice();
787 
788         VirtualMachineConfig config =
789                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
790                         .setMemoryBytes(minMemoryRequired())
791                         .setDebugLevel(DEBUG_LEVEL_FULL)
792                         .build();
793 
794         VirtualMachineManager vmm = getVirtualMachineManager();
795         String vmName = "vmName";
796 
797         try {
798             // VM does not yet exist
799             assertThat(vmm.get(vmName)).isNull();
800 
801             VirtualMachine vm1 = vmm.create(vmName, config);
802 
803             // Now it does, and we should get the same instance back
804             assertThat(vmm.get(vmName)).isSameInstanceAs(vm1);
805             assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm1);
806 
807             // Can't recreate it though
808             assertThrowsVmException(() -> vmm.create(vmName, config));
809 
810             vmm.delete(vmName);
811             assertThat(vmm.get(vmName)).isNull();
812 
813             // Now that we deleted the old one, this should create rather than get, and it should be
814             // a new instance.
815             VirtualMachine vm2 = vmm.getOrCreate(vmName, config);
816             assertThat(vm2).isNotSameInstanceAs(vm1);
817 
818             // The old one must remain deleted, or we'd have two VirtualMachine instances referring
819             // to the same VM.
820             assertThat(vm1.getStatus()).isEqualTo(STATUS_DELETED);
821 
822             // Subsequent gets should return this new one.
823             assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
824             assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
825         } finally {
826             vmm.delete(vmName);
827         }
828     }
829 
830     @Test
831     @CddTest(requirements = {"9.17/C-1-1"})
vmFilesStoredInDeDirWhenCreatedFromDEContext()832     public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
833         final Context ctx = getContext().createDeviceProtectedStorageContext();
834         final int userId = ctx.getUserId();
835         final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
836         VirtualMachineConfig config = newVmConfigBuilderWithPayloadBinary("binary.so").build();
837         try {
838             VirtualMachine vm = vmm.create("vm-name", config);
839             // TODO(b/261430346): what about non-primary user?
840             assertThat(vm.getRootDir().getAbsolutePath())
841                     .isEqualTo(
842                             "/data/user_de/" + userId + "/com.android.microdroid.test/vm/vm-name");
843         } finally {
844             vmm.delete("vm-name");
845         }
846     }
847 
848     @Test
849     @CddTest(requirements = {"9.17/C-1-1"})
vmFilesStoredInCeDirWhenCreatedFromCEContext()850     public void vmFilesStoredInCeDirWhenCreatedFromCEContext() throws Exception {
851         final Context ctx = getContext().createCredentialProtectedStorageContext();
852         final int userId = ctx.getUserId();
853         final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
854         VirtualMachineConfig config = newVmConfigBuilderWithPayloadBinary("binary.so").build();
855         try {
856             VirtualMachine vm = vmm.create("vm-name", config);
857             // TODO(b/261430346): what about non-primary user?
858             assertThat(vm.getRootDir().getAbsolutePath())
859                     .isEqualTo("/data/user/" + userId + "/com.android.microdroid.test/vm/vm-name");
860         } finally {
861             vmm.delete("vm-name");
862         }
863     }
864 
865     @Test
866     @CddTest(requirements = {"9.17/C-1-1"})
differentManagersForDifferentContexts()867     public void differentManagersForDifferentContexts() throws Exception {
868         final Context ceCtx = getContext().createCredentialProtectedStorageContext();
869         final Context deCtx = getContext().createDeviceProtectedStorageContext();
870         assertThat(ceCtx.getSystemService(VirtualMachineManager.class))
871                 .isNotSameInstanceAs(deCtx.getSystemService(VirtualMachineManager.class));
872     }
873 
874     @Test
875     @CddTest(
876             requirements = {
877                 "9.17/C-1-1",
878                 "9.17/C-1-2",
879                 "9.17/C-1-4",
880             })
createVmWithConfigRequiresPermission()881     public void createVmWithConfigRequiresPermission() throws Exception {
882         assumeSupportedDevice();
883         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
884 
885         VirtualMachineConfig config =
886                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
887                         .setMemoryBytes(minMemoryRequired())
888                         .build();
889 
890         VirtualMachine vm =
891                 forceCreateNewVirtualMachine("test_vm_config_requires_permission", config);
892 
893         SecurityException e =
894                 assertThrows(
895                         SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
896         assertThat(e)
897                 .hasMessageThat()
898                 .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
899     }
900 
901     @Test
902     @CddTest(
903             requirements = {
904                 "9.17/C-1-1",
905             })
deleteVm()906     public void deleteVm() throws Exception {
907         assumeSupportedDevice();
908 
909         VirtualMachineConfig config =
910                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
911                         .setMemoryBytes(minMemoryRequired())
912                         .build();
913 
914         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
915         VirtualMachineManager vmm = getVirtualMachineManager();
916         vmm.delete("test_vm_delete");
917 
918         // VM should no longer exist
919         assertThat(vmm.get("test_vm_delete")).isNull();
920 
921         // Can't start the VM even with an existing reference
922         assertThrowsVmException(vm::run);
923 
924         // Can't delete the VM since it no longer exists
925         assertThrowsVmException(() -> vmm.delete("test_vm_delete"));
926     }
927 
928     @Test
929     @CddTest(
930             requirements = {
931                 "9.17/C-1-1",
932             })
deleteVmFiles()933     public void deleteVmFiles() throws Exception {
934         assumeSupportedDevice();
935 
936         VirtualMachineConfig config =
937                 newVmConfigBuilderWithPayloadBinary("MicrodroidExitNativeLib.so")
938                         .setMemoryBytes(minMemoryRequired())
939                         .build();
940 
941         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
942         vm.run();
943         // If we explicitly stop a VM, that triggers some tidy up; so for this test we start a VM
944         // that immediately stops itself.
945         while (vm.getStatus() == STATUS_RUNNING) {
946             Thread.sleep(100);
947         }
948 
949         // Delete the files without telling VMM. This isn't a good idea, but we can't stop an
950         // app doing it, and we should recover from it.
951         for (File f : vm.getRootDir().listFiles()) {
952             Files.delete(f.toPath());
953         }
954         vm.getRootDir().delete();
955 
956         VirtualMachineManager vmm = getVirtualMachineManager();
957         assertThat(vmm.get("test_vm_delete")).isNull();
958         assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
959     }
960 
961     @Test
962     @CddTest(
963             requirements = {
964                 "9.17/C-1-1",
965             })
validApkPathIsAccepted()966     public void validApkPathIsAccepted() throws Exception {
967         assumeSupportedDevice();
968 
969         VirtualMachineConfig config =
970                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
971                         .setApkPath(getContext().getPackageCodePath())
972                         .setMemoryBytes(minMemoryRequired())
973                         .setDebugLevel(DEBUG_LEVEL_FULL)
974                         .build();
975 
976         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_explicit_apk_path", config);
977 
978         TestResults testResults =
979                 runVmTestService(
980                         TAG,
981                         vm,
982                         (ts, tr) -> {
983                             tr.mApkContentsPath = ts.getApkContentsPath();
984                         });
985         testResults.assertNoException();
986         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
987     }
988 
989     @Test
990     @CddTest(requirements = {"9.17/C-1-1"})
invalidVmNameIsRejected()991     public void invalidVmNameIsRejected() {
992         VirtualMachineManager vmm = getVirtualMachineManager();
993         assertThrows(IllegalArgumentException.class, () -> vmm.get("../foo"));
994         assertThrows(IllegalArgumentException.class, () -> vmm.get(".."));
995     }
996 
997     @Test
998     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
extraApk()999     public void extraApk() throws Exception {
1000         assumeSupportedDevice();
1001 
1002         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1003         VirtualMachineConfig config =
1004                 newVmConfigBuilderWithPayloadConfig("assets/vm_config_extra_apk.json")
1005                         .setMemoryBytes(minMemoryRequired())
1006                         .setDebugLevel(DEBUG_LEVEL_FULL)
1007                         .build();
1008         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_extra_apk", config);
1009 
1010         TestResults testResults =
1011                 runVmTestService(
1012                         TAG,
1013                         vm,
1014                         (ts, tr) -> {
1015                             tr.mExtraApkTestProp =
1016                                     ts.readProperty(
1017                                             "debug.microdroid.test.extra_apk_build_manifest");
1018                         });
1019         assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
1020     }
1021 
1022     @Test
1023     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
extraApkInVmConfig()1024     public void extraApkInVmConfig() throws Exception {
1025         assumeSupportedDevice();
1026         assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
1027 
1028         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1029         VirtualMachineConfig config =
1030                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1031                         .setMemoryBytes(minMemoryRequired())
1032                         .setDebugLevel(DEBUG_LEVEL_FULL)
1033                         .addExtraApk(VM_SHARE_APP_PACKAGE_NAME)
1034                         .build();
1035         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_extra_apk", config);
1036 
1037         TestResults testResults =
1038                 runVmTestService(
1039                         TAG,
1040                         vm,
1041                         (ts, tr) -> {
1042                             tr.mExtraApkTestProp =
1043                                     ts.readProperty("debug.microdroid.test.extra_apk_vm_share");
1044                         });
1045         assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
1046     }
1047 
1048     @Test
bootFailsWhenLowMem()1049     public void bootFailsWhenLowMem() throws Exception {
1050         for (int memMib : new int[] {10, 20, 40}) {
1051             VirtualMachineConfig lowMemConfig =
1052                     newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1053                             .setMemoryBytes(memMib)
1054                             .setDebugLevel(DEBUG_LEVEL_NONE)
1055                             .setVmOutputCaptured(false)
1056                             .build();
1057             VirtualMachine vm = forceCreateNewVirtualMachine("low_mem", lowMemConfig);
1058             final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
1059             final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
1060             VmEventListener listener =
1061                     new VmEventListener() {
1062                         @Override
1063                         public void onPayloadReady(VirtualMachine vm) {
1064                             onPayloadReadyExecuted.complete(true);
1065                             super.onPayloadReady(vm);
1066                         }
1067 
1068                         @Override
1069                         public void onStopped(VirtualMachine vm, int reason) {
1070                             onStoppedExecuted.complete(true);
1071                             super.onStopped(vm, reason);
1072                         }
1073                     };
1074             listener.runToFinish(TAG, vm);
1075             // Assert that onStopped() was executed but onPayloadReady() was never run
1076             assertThat(onStoppedExecuted.getNow(false)).isTrue();
1077             assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
1078         }
1079     }
1080 
1081     @Test
1082     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
changingNonDebuggableVmDebuggableInvalidatesVmIdentity()1083     public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
1084         // Debuggability changes initrd which is verified by pvmfw.
1085         // Therefore, skip this on non-protected VM.
1086         assumeProtectedVM();
1087         changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
1088     }
1089 
1090     // Copy the Vm directory, creating the target Vm directory if it does not already exist.
copyVmDirectory(String sourceVmName, String targetVmName)1091     private void copyVmDirectory(String sourceVmName, String targetVmName) throws IOException {
1092         Path sourceVm = getVmDirectory(sourceVmName);
1093         Path targetVm = getVmDirectory(targetVmName);
1094         if (!Files.exists(targetVm)) {
1095             Files.createDirectories(targetVm);
1096         }
1097 
1098         try (Stream<Path> stream = Files.list(sourceVm)) {
1099             for (Path f : stream.collect(toList())) {
1100                 Files.copy(f, targetVm.resolve(f.getFileName()), REPLACE_EXISTING);
1101             }
1102         }
1103     }
1104 
getVmDirectory(String vmName)1105     private Path getVmDirectory(String vmName) {
1106         Context context = getContext();
1107         Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName);
1108         return filePath;
1109     }
1110 
1111     // Create a fresh VM with the given `vmName`, instance_id & instance.img. This function creates
1112     // a Vm with a different temporary name & copies it to target VM directory. This ensures this
1113     // VM is not in cache of `VirtualMachineManager` which makes it possible to modify underlying
1114     // files.
createUncachedVmWithName( String vmName, VirtualMachineConfig config, File vmIdBackup, File vmInstanceBackup)1115     private void createUncachedVmWithName(
1116             String vmName, VirtualMachineConfig config, File vmIdBackup, File vmInstanceBackup)
1117             throws Exception {
1118         deleteVirtualMachineIfExists(vmName);
1119         forceCreateNewVirtualMachine("test_vm_tmp", config);
1120         copyVmDirectory("test_vm_tmp", vmName);
1121         if (vmInstanceBackup != null) {
1122             Files.copy(
1123                     vmInstanceBackup.toPath(),
1124                     getVmFile(vmName, "instance.img").toPath(),
1125                     REPLACE_EXISTING);
1126         }
1127         if (vmIdBackup != null) {
1128             Files.copy(
1129                     vmIdBackup.toPath(),
1130                     getVmFile(vmName, "instance_id").toPath(),
1131                     REPLACE_EXISTING);
1132         }
1133     }
1134 
1135     @Test
1136     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
changingDebuggableVmNonDebuggableInvalidatesVmIdentity()1137     public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
1138         // Debuggability changes initrd which is verified by pvmfw.
1139         // Therefore, skip this on non-protected VM.
1140         assumeProtectedVM();
1141         changeDebugLevel(DEBUG_LEVEL_FULL, DEBUG_LEVEL_NONE);
1142     }
1143 
changeDebugLevel(int fromLevel, int toLevel)1144     private void changeDebugLevel(int fromLevel, int toLevel) throws Exception {
1145         assumeSupportedDevice();
1146 
1147         VirtualMachineConfig.Builder builder =
1148                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1149                         .setDebugLevel(fromLevel)
1150                         .setVmOutputCaptured(false);
1151         VirtualMachineConfig normalConfig = builder.build();
1152         assertThat(tryBootVmWithConfig(normalConfig, "test_vm").payloadStarted).isTrue();
1153 
1154         // Try to run the VM again with the previous instance
1155         // We need to make sure that no changes on config don't invalidate the identity, to compare
1156         // the result with the below "different debug level" test.
1157         File vmInstanceBackup = null, vmIdBackup = null;
1158         File vmInstance = getVmFile("test_vm", "instance.img");
1159         File vmId = getVmFile("test_vm", "instance_id");
1160         if (vmInstance.exists()) {
1161             vmInstanceBackup = File.createTempFile("instance", ".img");
1162             Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
1163         }
1164         if (vmId.exists()) {
1165             vmIdBackup = File.createTempFile("instance_id", "backup");
1166             Files.copy(vmId.toPath(), vmIdBackup.toPath(), REPLACE_EXISTING);
1167         }
1168 
1169         createUncachedVmWithName("test_vm_rerun", normalConfig, vmIdBackup, vmInstanceBackup);
1170         assertThat(tryBootVm(TAG, "test_vm_rerun").payloadStarted).isTrue();
1171 
1172         // Launch the same VM with a different debug level. The Java API prohibits this
1173         // (thankfully).
1174         // For testing, we do that by creating a new VM with debug level, and overwriting the old
1175         // instance data to the new VM instance data.
1176         VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
1177         createUncachedVmWithName(
1178                 "test_vm_changed_debug_level", debugConfig, vmIdBackup, vmInstanceBackup);
1179         assertThat(tryBootVm(TAG, "test_vm_changed_debug_level").payloadStarted).isFalse();
1180     }
1181 
1182     private static class VmCdis {
1183         public byte[] cdiAttest;
1184         public byte[] instanceSecret;
1185     }
1186 
launchVmAndGetCdis(String instanceName)1187     private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
1188         VirtualMachine vm = getVirtualMachineManager().get(instanceName);
1189         VmCdis vmCdis = new VmCdis();
1190         CompletableFuture<Exception> exception = new CompletableFuture<>();
1191         VmEventListener listener =
1192                 new VmEventListener() {
1193                     @Override
1194                     public void onPayloadReady(VirtualMachine vm) {
1195                         try {
1196                             ITestService testService =
1197                                     ITestService.Stub.asInterface(
1198                                             vm.connectToVsockServer(ITestService.PORT));
1199                             vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi();
1200                             vmCdis.instanceSecret = testService.insecurelyExposeVmInstanceSecret();
1201                         } catch (Exception e) {
1202                             exception.complete(e);
1203                         } finally {
1204                             forceStop(vm);
1205                         }
1206                     }
1207                 };
1208         listener.runToFinish(TAG, vm);
1209         Exception e = exception.getNow(null);
1210         if (e != null) {
1211             throw new RuntimeException(e);
1212         }
1213         return vmCdis;
1214     }
1215 
1216     @Test
1217     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7", "9.17/C-3-4"})
instancesOfSameVmHaveDifferentCdis()1218     public void instancesOfSameVmHaveDifferentCdis() throws Exception {
1219         assumeSupportedDevice();
1220         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
1221         // `instance-id` which is input to DICE is contained in DT which is missing in CF.
1222         assumeFalse(
1223                 "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
1224                 isCuttlefish() || isGoldfish());
1225 
1226         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1227         VirtualMachineConfig normalConfig =
1228                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
1229                         .setDebugLevel(DEBUG_LEVEL_FULL)
1230                         .build();
1231         forceCreateNewVirtualMachine("test_vm_a", normalConfig);
1232         forceCreateNewVirtualMachine("test_vm_b", normalConfig);
1233         VmCdis vm_a_cdis = launchVmAndGetCdis("test_vm_a");
1234         VmCdis vm_b_cdis = launchVmAndGetCdis("test_vm_b");
1235         assertThat(vm_a_cdis.cdiAttest).isNotNull();
1236         assertThat(vm_b_cdis.cdiAttest).isNotNull();
1237         assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiAttest);
1238         assertThat(vm_a_cdis.instanceSecret).isNotNull();
1239         assertThat(vm_b_cdis.instanceSecret).isNotNull();
1240         assertThat(vm_a_cdis.instanceSecret).isNotEqualTo(vm_b_cdis.instanceSecret);
1241     }
1242 
1243     @Test
1244     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7", "9.17/C-3-4"})
sameInstanceKeepsSameCdis()1245     public void sameInstanceKeepsSameCdis() throws Exception {
1246         assumeSupportedDevice();
1247         assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
1248 
1249         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1250         VirtualMachineConfig normalConfig =
1251                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
1252                         .setDebugLevel(DEBUG_LEVEL_FULL)
1253                         .build();
1254         forceCreateNewVirtualMachine("test_vm", normalConfig);
1255 
1256         VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
1257         VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
1258         // The attestation CDI isn't specified to be stable, though it might be
1259         assertThat(first_boot_cdis.instanceSecret).isNotNull();
1260         assertThat(second_boot_cdis.instanceSecret).isNotNull();
1261         assertThat(first_boot_cdis.instanceSecret).isEqualTo(second_boot_cdis.instanceSecret);
1262     }
1263 
1264     @Test
1265     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
bccIsSuperficiallyWellFormed()1266     public void bccIsSuperficiallyWellFormed() throws Exception {
1267         assumeSupportedDevice();
1268 
1269         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1270         VirtualMachineConfig normalConfig =
1271                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
1272                         .setDebugLevel(DEBUG_LEVEL_FULL)
1273                         .build();
1274         VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm", normalConfig);
1275         TestResults testResults =
1276                 runVmTestService(
1277                         TAG,
1278                         vm,
1279                         (service, results) -> {
1280                             results.mBcc = service.getBcc();
1281                         });
1282         testResults.assertNoException();
1283         byte[] bccBytes = testResults.mBcc;
1284         assertThat(bccBytes).isNotNull();
1285 
1286         ByteArrayInputStream bais = new ByteArrayInputStream(bccBytes);
1287         List<DataItem> dataItems = new CborDecoder(bais).decode();
1288         assertThat(dataItems.size()).isEqualTo(1);
1289         assertThat(dataItems.get(0).getMajorType()).isEqualTo(MajorType.ARRAY);
1290         List<DataItem> rootArrayItems = ((Array) dataItems.get(0)).getDataItems();
1291         int diceChainSize = rootArrayItems.size();
1292         assertThat(diceChainSize).isAtLeast(2); // Root public key and one certificate
1293         if (mProtectedVm) {
1294             if (isFeatureEnabled(VirtualMachineManager.FEATURE_DICE_CHANGES)) {
1295                 // We expect the root public key, at least one entry for the boot before pvmfw,
1296                 // then pvmfw, vm_entry (Microdroid kernel) and Microdroid payload entries.
1297                 // Before Android V we did not require that vendor code contain any DICE entries
1298                 // preceding pvmfw, so the minimum is one less.
1299                 int minDiceChainSize = getVendorApiLevel() > 202404 ? 5 : 4;
1300                 assertThat(diceChainSize).isAtLeast(minDiceChainSize);
1301             } else {
1302                 // pvmfw truncates the DICE chain it gets, so we expect exactly entries for
1303                 // public key, vm_entry (Microdroid kernel) and Microdroid payload.
1304                 assertThat(diceChainSize).isEqualTo(3);
1305             }
1306         }
1307     }
1308 
1309     @Test
1310     @VsrTest(requirements = {"VSR-7.1-001.004"})
protectedVmHasValidDiceChain()1311     public void protectedVmHasValidDiceChain() throws Exception {
1312         // This test validates two things regarding the pVM DICE chain:
1313         // 1. The DICE chain is well-formed that all the entries conform to the DICE spec.
1314         // 2. Each entry in the DICE chain is signed by the previous entry's subject public key.
1315         assumeSupportedDevice();
1316         assumeProtectedVM();
1317         assumeVsrCompliant();
1318         assumeTrue("Vendor API must be newer than 202404", getVendorApiLevel() > 202404);
1319 
1320         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1321         VirtualMachineConfig config =
1322                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
1323                         .setDebugLevel(DEBUG_LEVEL_NONE)
1324                         .build();
1325         VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm_for_vsr", config);
1326         TestResults testResults =
1327                 runVmTestService(
1328                         TAG,
1329                         vm,
1330                         (service, results) -> {
1331                             results.mBcc = service.getBcc();
1332                         });
1333         testResults.assertNoException();
1334         byte[] bccBytes = testResults.mBcc;
1335         assertThat(bccBytes).isNotNull();
1336 
1337         String buildType = SystemProperties.get("ro.build.type");
1338         boolean nonUserBuild = !buildType.isEmpty() && buildType != "user";
1339 
1340         assertThat(HwTrustJni.validateDiceChain(bccBytes, nonUserBuild)).isTrue();
1341     }
1342 
1343     @Test
1344     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2"})
accessToCdisIsRestricted()1345     public void accessToCdisIsRestricted() throws Exception {
1346         assumeSupportedDevice();
1347 
1348         VirtualMachineConfig config =
1349                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1350                         .setDebugLevel(DEBUG_LEVEL_FULL)
1351                         .build();
1352         forceCreateNewVirtualMachine("test_vm", config);
1353 
1354         assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
1355     }
1356 
1357     private static final UUID MICRODROID_PARTITION_UUID =
1358             UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
1359     private static final UUID PVM_FW_PARTITION_UUID =
1360             UUID.fromString("90d2174a-038a-4bc6-adf3-824848fc5825");
1361     private static final long BLOCK_SIZE = 512;
1362 
1363     // Find the starting offset which holds the data of a partition having UUID.
1364     // This is a kind of hack; rather than parsing QCOW2 we exploit the fact that the cluster size
1365     // is normally greater than 512. It implies that the partition data should exist at a block
1366     // which follows the header block
findPartitionDataOffset(RandomAccessFile file, UUID uuid)1367     private OptionalLong findPartitionDataOffset(RandomAccessFile file, UUID uuid)
1368             throws IOException {
1369         // For each 512-byte block in file, check header
1370         long fileSize = file.length();
1371 
1372         for (long idx = 0; idx + BLOCK_SIZE < fileSize; idx += BLOCK_SIZE) {
1373             file.seek(idx);
1374             long high = file.readLong();
1375             long low = file.readLong();
1376             if (uuid.equals(new UUID(high, low))) return OptionalLong.of(idx + BLOCK_SIZE);
1377         }
1378         return OptionalLong.empty();
1379     }
1380 
flipBit(RandomAccessFile file, long offset)1381     private void flipBit(RandomAccessFile file, long offset) throws IOException {
1382         file.seek(offset);
1383         int b = file.readByte();
1384         file.seek(offset);
1385         file.writeByte(b ^ 1);
1386     }
1387 
prepareInstanceImage(String vmName)1388     private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
1389         VirtualMachineConfig config =
1390                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1391                         .setDebugLevel(DEBUG_LEVEL_FULL)
1392                         .build();
1393 
1394         assertThat(tryBootVmWithConfig(config, vmName).payloadStarted).isTrue();
1395         File instanceImgPath = getVmFile(vmName, "instance.img");
1396         return new RandomAccessFile(instanceImgPath, "rw");
1397     }
1398 
assertThatPartitionIsMissing(UUID partitionUuid)1399     private void assertThatPartitionIsMissing(UUID partitionUuid) throws Exception {
1400         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
1401         assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent()).isFalse();
1402     }
1403 
1404     // Flips a bit of given partition, and then see if boot fails.
assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)1405     private void assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)
1406             throws Exception {
1407         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
1408         OptionalLong offset = findPartitionDataOffset(instanceFile, partitionUuid);
1409         assertThat(offset.isPresent()).isTrue();
1410 
1411         flipBit(instanceFile, offset.getAsLong());
1412 
1413         BootResult result = tryBootVm(TAG, "test_vm_integrity");
1414         assertThat(result.payloadStarted).isFalse();
1415 
1416         // This failure should shut the VM down immediately and shouldn't trigger a hangup.
1417         assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.STOP_REASON_HANGUP);
1418     }
1419 
1420     @Test
1421     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
bootFailsWhenMicrodroidDataIsCompromised()1422     public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
1423         // If Updatable VM is supported => No instance.img required
1424         assumeNoUpdatableVmSupport();
1425         assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
1426     }
1427 
1428     @Test
1429     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
bootFailsWhenPvmFwDataIsCompromised()1430     public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
1431         // If Updatable VM is supported => No instance.img required
1432         assumeNoUpdatableVmSupport();
1433         if (mProtectedVm) {
1434             assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID);
1435         } else {
1436             // non-protected VM shouldn't have pvmfw data
1437             assertThatPartitionIsMissing(PVM_FW_PARTITION_UUID);
1438         }
1439     }
1440 
1441     @Test
bootFailsWhenConfigIsInvalid()1442     public void bootFailsWhenConfigIsInvalid() throws Exception {
1443         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1444         VirtualMachineConfig config =
1445                 newVmConfigBuilderWithPayloadConfig("assets/vm_config_no_task.json")
1446                         .setDebugLevel(DEBUG_LEVEL_FULL)
1447                         .build();
1448 
1449         BootResult bootResult = tryBootVmWithConfig(config, "test_vm_invalid_config");
1450         assertThat(bootResult.payloadStarted).isFalse();
1451         assertThat(bootResult.deathReason)
1452                 .isEqualTo(VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
1453     }
1454 
1455     @Test
bootFailsWhenBinaryNameIsInvalid()1456     public void bootFailsWhenBinaryNameIsInvalid() throws Exception {
1457         VirtualMachineConfig config =
1458                 newVmConfigBuilderWithPayloadBinary("DoesNotExist.so")
1459                         .setDebugLevel(DEBUG_LEVEL_FULL)
1460                         .build();
1461 
1462         BootResult bootResult = tryBootVmWithConfig(config, "test_vm_invalid_binary_path");
1463         assertThat(bootResult.payloadStarted).isFalse();
1464         assertThat(bootResult.deathReason)
1465                 .isEqualTo(VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR);
1466     }
1467 
1468     @Test
bootFailsWhenApkPathIsInvalid()1469     public void bootFailsWhenApkPathIsInvalid() {
1470         VirtualMachineConfig config =
1471                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1472                         .setDebugLevel(DEBUG_LEVEL_FULL)
1473                         .setApkPath("/does/not/exist")
1474                         .build();
1475 
1476         assertThrowsVmExceptionContaining(
1477                 () -> tryBootVmWithConfig(config, "test_vm_invalid_apk_path"),
1478                 "Failed to open APK");
1479     }
1480 
1481     @Test
bootFailsWhenExtraApkPackageIsInvalid()1482     public void bootFailsWhenExtraApkPackageIsInvalid() {
1483         VirtualMachineConfig config =
1484                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1485                         .setDebugLevel(DEBUG_LEVEL_FULL)
1486                         .addExtraApk("com.example.nosuch.package")
1487                         .build();
1488         assertThrowsVmExceptionContaining(
1489                 () -> tryBootVmWithConfig(config, "test_vm_invalid_extra_apk_package"),
1490                 "Extra APK package not found");
1491     }
1492 
tryBootVmWithConfig(VirtualMachineConfig config, String vmName)1493     private BootResult tryBootVmWithConfig(VirtualMachineConfig config, String vmName)
1494             throws Exception {
1495         try (VirtualMachine ignored = forceCreateNewVirtualMachine(vmName, config)) {
1496             return tryBootVm(TAG, vmName);
1497         }
1498     }
1499 
1500     // Checks whether microdroid_launcher started but payload failed. reason must be recorded in the
1501     // console output.
assertThatPayloadFailsDueTo(VirtualMachine vm, String reason)1502     private void assertThatPayloadFailsDueTo(VirtualMachine vm, String reason) throws Exception {
1503         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
1504         final CompletableFuture<Integer> exitCodeFuture = new CompletableFuture<>();
1505         VmEventListener listener =
1506                 new VmEventListener() {
1507                     @Override
1508                     public void onPayloadStarted(VirtualMachine vm) {
1509                         payloadStarted.complete(true);
1510                     }
1511 
1512                     @Override
1513                     public void onPayloadFinished(VirtualMachine vm, int exitCode) {
1514                         exitCodeFuture.complete(exitCode);
1515                     }
1516                 };
1517         listener.runToFinish(TAG, vm);
1518 
1519         assertThat(payloadStarted.getNow(false)).isTrue();
1520         assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
1521         assertThat(listener.getConsoleOutput() + listener.getLogOutput()).contains(reason);
1522     }
1523 
1524     @Test
bootFailsWhenBinaryIsMissingEntryFunction()1525     public void bootFailsWhenBinaryIsMissingEntryFunction() throws Exception {
1526         VirtualMachineConfig normalConfig =
1527                 newVmConfigBuilderWithPayloadBinary("MicrodroidEmptyNativeLib.so")
1528                         .setDebugLevel(DEBUG_LEVEL_FULL)
1529                         .setVmOutputCaptured(true)
1530                         .build();
1531         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_missing_entry", normalConfig);
1532 
1533         assertThatPayloadFailsDueTo(vm, "Failed to find entrypoint");
1534     }
1535 
1536     @Test
bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs()1537     public void bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs() throws Exception {
1538         VirtualMachineConfig normalConfig =
1539                 newVmConfigBuilderWithPayloadBinary("MicrodroidPrivateLinkingNativeLib.so")
1540                         .setDebugLevel(DEBUG_LEVEL_FULL)
1541                         .setVmOutputCaptured(true)
1542                         .build();
1543         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_private_linking", normalConfig);
1544 
1545         assertThatPayloadFailsDueTo(vm, "Failed to dlopen");
1546     }
1547 
1548     @Test
sameInstancesShareTheSameVmObject()1549     public void sameInstancesShareTheSameVmObject() throws Exception {
1550         VirtualMachineConfig config =
1551                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so").build();
1552 
1553         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1554         VirtualMachine vm2 = getVirtualMachineManager().get("test_vm");
1555         assertThat(vm).isEqualTo(vm2);
1556 
1557         VirtualMachine newVm = forceCreateNewVirtualMachine("test_vm", config);
1558         VirtualMachine newVm2 = getVirtualMachineManager().get("test_vm");
1559         assertThat(newVm).isEqualTo(newVm2);
1560 
1561         assertThat(vm).isNotEqualTo(newVm);
1562     }
1563 
1564     @Test
importedVmAndOriginalVmHaveTheSameCdi()1565     public void importedVmAndOriginalVmHaveTheSameCdi() throws Exception {
1566         assumeSupportedDevice();
1567         // Arrange
1568         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1569         VirtualMachineConfig config =
1570                 newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
1571                         .setDebugLevel(DEBUG_LEVEL_FULL)
1572                         .build();
1573         String vmNameOrig = "test_vm_orig";
1574         String vmNameImport = "test_vm_import";
1575         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
1576         VmCdis origCdis = launchVmAndGetCdis(vmNameOrig);
1577         assertThat(origCdis.instanceSecret).isNotNull();
1578         VirtualMachineManager vmm = getVirtualMachineManager();
1579         if (vmm.get(vmNameImport) != null) {
1580             vmm.delete(vmNameImport);
1581         }
1582 
1583         // Action
1584         // The imported VM will be fetched by name later.
1585         vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
1586 
1587         // Asserts
1588         VmCdis importCdis = launchVmAndGetCdis(vmNameImport);
1589         assertThat(origCdis.instanceSecret).isEqualTo(importCdis.instanceSecret);
1590     }
1591 
1592     @Test
1593     @CddTest(requirements = {"9.17/C-1-1"})
importedVmIsEqualToTheOriginalVm_WithoutStorage()1594     public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
1595         TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
1596         assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
1597     }
1598 
1599     @Test
1600     @CddTest(requirements = {"9.17/C-1-1"})
importedVmIsEqualToTheOriginalVm_WithStorage()1601     public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
1602         TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
1603         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
1604     }
1605 
importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)1606     private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
1607             throws Exception {
1608         // Arrange
1609         VirtualMachineConfig.Builder builder =
1610                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1611                         .setDebugLevel(DEBUG_LEVEL_FULL);
1612         if (encryptedStoreEnabled) {
1613             builder.setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES);
1614         }
1615         VirtualMachineConfig config = builder.build();
1616         String vmNameOrig = "test_vm_orig";
1617         String vmNameImport = "test_vm_import";
1618         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
1619         // Run something to make the instance.img different with the initialized one.
1620         TestResults origTestResults =
1621                 runVmTestService(
1622                         TAG,
1623                         vmOrig,
1624                         (ts, tr) -> {
1625                             tr.mAddInteger = ts.addInteger(123, 456);
1626                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1627                         });
1628         origTestResults.assertNoException();
1629         assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
1630         VirtualMachineManager vmm = getVirtualMachineManager();
1631         if (vmm.get(vmNameImport) != null) {
1632             vmm.delete(vmNameImport);
1633         }
1634 
1635         // Action
1636         VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
1637 
1638         // Asserts
1639         assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
1640         assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
1641         if (encryptedStoreEnabled) {
1642             assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
1643         }
1644         assertThat(vmImport).isNotEqualTo(vmOrig);
1645         assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
1646         TestResults testResults =
1647                 runVmTestService(
1648                         TAG,
1649                         vmImport,
1650                         (ts, tr) -> {
1651                             tr.mAddInteger = ts.addInteger(123, 456);
1652                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1653                         });
1654         testResults.assertNoException();
1655         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
1656         return testResults;
1657     }
1658 
1659     @Test
1660     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageAvailable()1661     public void encryptedStorageAvailable() throws Exception {
1662         assumeSupportedDevice();
1663 
1664         VirtualMachineConfig config =
1665                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1666                         .setMemoryBytes(minMemoryRequired())
1667                         .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
1668                         .setDebugLevel(DEBUG_LEVEL_FULL)
1669                         .build();
1670         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1671 
1672         TestResults testResults =
1673                 runVmTestService(
1674                         TAG,
1675                         vm,
1676                         (ts, tr) -> {
1677                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1678                         });
1679         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
1680     }
1681 
1682     @Test
1683     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageIsInaccessibleToDifferentVm()1684     public void encryptedStorageIsInaccessibleToDifferentVm() throws Exception {
1685         assumeSupportedDevice();
1686         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
1687         // `instance-id` which is input to DICE is contained in DT which is missing in CF.
1688         assumeFalse(
1689                 "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
1690                 isCuttlefish() || isGoldfish());
1691 
1692         VirtualMachineConfig config =
1693                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1694                         .setMemoryBytes(minMemoryRequired())
1695                         .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
1696                         .setDebugLevel(DEBUG_LEVEL_FULL)
1697                         .build();
1698 
1699         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1700 
1701         TestResults testResults =
1702                 runVmTestService(
1703                         TAG,
1704                         vm,
1705                         (ts, tr) -> {
1706                             ts.writeToFile(
1707                                     /* content= */ EXAMPLE_STRING,
1708                                     /* path= */ "/mnt/encryptedstore/test_file");
1709                         });
1710         testResults.assertNoException();
1711 
1712         // Start a different vm (this changes the vm identity)
1713         VirtualMachine diff_test_vm = forceCreateNewVirtualMachine("diff_test_vm", config);
1714 
1715         // Replace the backing storage image to the original one
1716         File storageImgOrig = getVmFile("test_vm", "storage.img");
1717         File storageImgNew = getVmFile("diff_test_vm", "storage.img");
1718         Files.copy(storageImgOrig.toPath(), storageImgNew.toPath(), REPLACE_EXISTING);
1719         assertFileContentsAreEqualInTwoVms("storage.img", "test_vm", "diff_test_vm");
1720 
1721         CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
1722         CompletableFuture<Boolean> onErrorExecuted = new CompletableFuture<>();
1723         CompletableFuture<String> errorMessage = new CompletableFuture<>();
1724         VmEventListener listener =
1725                 new VmEventListener() {
1726                     @Override
1727                     public void onPayloadReady(VirtualMachine vm) {
1728                         onPayloadReadyExecuted.complete(true);
1729                         super.onPayloadReady(vm);
1730                     }
1731 
1732                     @Override
1733                     public void onError(VirtualMachine vm, int errorCode, String message) {
1734                         onErrorExecuted.complete(true);
1735                         errorMessage.complete(message);
1736                         super.onError(vm, errorCode, message);
1737                     }
1738                 };
1739         listener.runToFinish(TAG, diff_test_vm);
1740 
1741         // Assert that payload never started & error message reflects storage error.
1742         assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
1743         assertThat(onErrorExecuted.getNow(false)).isTrue();
1744         assertThat(errorMessage.getNow("")).contains("Unable to prepare encrypted storage");
1745     }
1746 
1747     @Test
1748     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
microdroidLauncherHasEmptyCapabilities()1749     public void microdroidLauncherHasEmptyCapabilities() throws Exception {
1750         assumeSupportedDevice();
1751 
1752         final VirtualMachineConfig vmConfig =
1753                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1754                         .setMemoryBytes(minMemoryRequired())
1755                         .setDebugLevel(DEBUG_LEVEL_FULL)
1756                         .build();
1757         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_caps", vmConfig);
1758 
1759         final TestResults testResults =
1760                 runVmTestService(
1761                         TAG,
1762                         vm,
1763                         (ts, tr) -> {
1764                             tr.mEffectiveCapabilities = ts.getEffectiveCapabilities();
1765                         });
1766 
1767         testResults.assertNoException();
1768         assertThat(testResults.mEffectiveCapabilities).isEmpty();
1769     }
1770 
1771     @Test
1772     @CddTest(requirements = {"9.17/C-1-1"})
payloadIsNotRoot()1773     public void payloadIsNotRoot() throws Exception {
1774         assumeSupportedDevice();
1775         assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
1776 
1777         VirtualMachineConfig config =
1778                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1779                         .setMemoryBytes(minMemoryRequired())
1780                         .setDebugLevel(DEBUG_LEVEL_FULL)
1781                         .build();
1782         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1783         TestResults testResults =
1784                 runVmTestService(
1785                         TAG,
1786                         vm,
1787                         (ts, tr) -> {
1788                             tr.mUid = ts.getUid();
1789                         });
1790         testResults.assertNoException();
1791         assertThat(testResults.mUid).isNotEqualTo(0);
1792     }
1793 
1794     @Test
1795     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageIsPersistent()1796     public void encryptedStorageIsPersistent() throws Exception {
1797         assumeSupportedDevice();
1798 
1799         VirtualMachineConfig config =
1800                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1801                         .setMemoryBytes(minMemoryRequired())
1802                         .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
1803                         .setDebugLevel(DEBUG_LEVEL_FULL)
1804                         .build();
1805         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_a", config);
1806         TestResults testResults =
1807                 runVmTestService(
1808                         TAG,
1809                         vm,
1810                         (ts, tr) -> {
1811                             ts.writeToFile(
1812                                     /* content= */ EXAMPLE_STRING,
1813                                     /* path= */ "/mnt/encryptedstore/test_file");
1814                         });
1815         testResults.assertNoException();
1816 
1817         // Re-run the same VM & verify the file persisted. Note, the previous `runVmTestService`
1818         // stopped the VM
1819         testResults =
1820                 runVmTestService(
1821                         TAG,
1822                         vm,
1823                         (ts, tr) -> {
1824                             tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
1825                         });
1826         testResults.assertNoException();
1827         assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
1828     }
1829 
1830     @Test
1831     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
canReadFileFromAssets_debugFull()1832     public void canReadFileFromAssets_debugFull() throws Exception {
1833         assumeSupportedDevice();
1834 
1835         VirtualMachineConfig config =
1836                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1837                         .setMemoryBytes(minMemoryRequired())
1838                         .setDebugLevel(DEBUG_LEVEL_FULL)
1839                         .build();
1840         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_read_from_assets", config);
1841 
1842         TestResults testResults =
1843                 runVmTestService(
1844                         TAG,
1845                         vm,
1846                         (testService, ts) -> {
1847                             ts.mFileContent = testService.readFromFile("/mnt/apk/assets/file.txt");
1848                         });
1849 
1850         testResults.assertNoException();
1851         assertThat(testResults.mFileContent).isEqualTo("Hello, I am a file!");
1852     }
1853 
1854     @Test
outputShouldBeExplicitlyCaptured()1855     public void outputShouldBeExplicitlyCaptured() throws Exception {
1856         assumeSupportedDevice();
1857 
1858         final VirtualMachineConfig vmConfig =
1859                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1860                         .setDebugLevel(DEBUG_LEVEL_FULL)
1861                         .setVmConsoleInputSupported(true) // even if console input is supported
1862                         .build();
1863         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_forward_log", vmConfig);
1864         vm.run();
1865 
1866         try {
1867             assertThrowsVmExceptionContaining(
1868                     () -> vm.getConsoleOutput(), "Capturing vm outputs is turned off");
1869             assertThrowsVmExceptionContaining(
1870                     () -> vm.getLogOutput(), "Capturing vm outputs is turned off");
1871         } finally {
1872             vm.stop();
1873         }
1874     }
1875 
1876     @Test
inputShouldBeExplicitlyAllowed()1877     public void inputShouldBeExplicitlyAllowed() throws Exception {
1878         assumeSupportedDevice();
1879 
1880         final VirtualMachineConfig vmConfig =
1881                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1882                         .setDebugLevel(DEBUG_LEVEL_FULL)
1883                         .setVmOutputCaptured(true) // even if output is captured
1884                         .build();
1885         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_forward_log", vmConfig);
1886         vm.run();
1887 
1888         try {
1889             assertThrowsVmExceptionContaining(
1890                     () -> vm.getConsoleInput(), "VM console input is not supported");
1891         } finally {
1892             vm.stop();
1893         }
1894     }
1895 
checkVmOutputIsRedirectedToLogcat(boolean debuggable)1896     private boolean checkVmOutputIsRedirectedToLogcat(boolean debuggable) throws Exception {
1897         String time =
1898                 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
1899         final VirtualMachineConfig vmConfig =
1900                 new VirtualMachineConfig.Builder(getContext())
1901                         .setProtectedVm(mProtectedVm)
1902                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1903                         .setDebugLevel(debuggable ? DEBUG_LEVEL_FULL : DEBUG_LEVEL_NONE)
1904                         .setVmOutputCaptured(false)
1905                         .setOs(os())
1906                         .build();
1907         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_logcat", vmConfig);
1908 
1909         runVmTestService(TAG, vm, (service, results) -> {});
1910 
1911         // only check logs printed after this test
1912         Process logcatProcess =
1913                 new ProcessBuilder()
1914                         .command(
1915                                 "logcat",
1916                                 "-e",
1917                                 "virtualizationmanager::aidl: (Console|Log).*executing main task",
1918                                 "-t",
1919                                 time)
1920                         .start();
1921         logcatProcess.waitFor();
1922         BufferedReader reader =
1923                 new BufferedReader(new InputStreamReader(logcatProcess.getInputStream()));
1924         return !Strings.isNullOrEmpty(reader.readLine());
1925     }
1926 
1927     @Test
outputIsRedirectedToLogcatIfNotCaptured()1928     public void outputIsRedirectedToLogcatIfNotCaptured() throws Exception {
1929         assumeSupportedDevice();
1930 
1931         assertThat(checkVmOutputIsRedirectedToLogcat(true)).isTrue();
1932     }
1933 
setSystemProperties(String name, String value)1934     private boolean setSystemProperties(String name, String value) {
1935         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
1936         UiAutomation uiAutomation = instrumentation.getUiAutomation();
1937         String cmd = "setprop " + name + " " + (value.isEmpty() ? "\"\"" : value);
1938         return runInShellWithStderr(TAG, uiAutomation, cmd).trim().isEmpty();
1939     }
1940 
1941     @Test
1942     @Ignore("b/372874464")
outputIsNotRedirectedToLogcatIfNotDebuggable()1943     public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
1944         assumeSupportedDevice();
1945 
1946         // Disable debug policy to ensure no log output.
1947         String sysprop = "hypervisor.virtualizationmanager.debug_policy.path";
1948         String old = SystemProperties.get(sysprop);
1949         assumeTrue(
1950                 "Can't disable debug policy. Perhapse user build?",
1951                 setSystemProperties(sysprop, ""));
1952 
1953         try {
1954             assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
1955         } finally {
1956             assertThat(setSystemProperties(sysprop, old)).isTrue();
1957         }
1958     }
1959 
1960     @Test
testConsoleInputSupported()1961     public void testConsoleInputSupported() throws Exception {
1962         assumeSupportedDevice();
1963         assumeFalse("Not supported on GKI kernels", mOs.startsWith("microdroid_gki-"));
1964 
1965         VirtualMachineConfig config =
1966                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
1967                         .setDebugLevel(DEBUG_LEVEL_FULL)
1968                         .setVmConsoleInputSupported(true)
1969                         .setVmOutputCaptured(true)
1970                         .build();
1971         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_console_in", config);
1972 
1973         final String TYPED = "this is a console input\n";
1974         TestResults testResults =
1975                 runVmTestService(
1976                         TAG,
1977                         vm,
1978                         (ts, tr) -> {
1979                             OutputStreamWriter consoleIn =
1980                                     new OutputStreamWriter(vm.getConsoleInput());
1981                             consoleIn.write(TYPED);
1982                             consoleIn.close();
1983                             tr.mConsoleInput = ts.readLineFromConsole();
1984                         });
1985         testResults.assertNoException();
1986         assertThat(testResults.mConsoleInput).isEqualTo(TYPED);
1987     }
1988 
1989     @Test
testStartVmWithPayloadOfAnotherApp()1990     public void testStartVmWithPayloadOfAnotherApp() throws Exception {
1991         assumeSupportedDevice();
1992 
1993         Context ctx = getContext();
1994         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
1995 
1996         VirtualMachineConfig config =
1997                 new VirtualMachineConfig.Builder(otherAppCtx)
1998                         .setDebugLevel(DEBUG_LEVEL_FULL)
1999                         .setProtectedVm(isProtectedVm())
2000                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
2001                         .setOs(os())
2002                         .build();
2003 
2004         try (VirtualMachine vm = forceCreateNewVirtualMachine("vm_from_another_app", config)) {
2005             TestResults results =
2006                     runVmTestService(
2007                             TAG,
2008                             vm,
2009                             (ts, tr) -> {
2010                                 tr.mAddInteger = ts.addInteger(101, 303);
2011                             });
2012             assertThat(results.mAddInteger).isEqualTo(404);
2013         }
2014 
2015         getVirtualMachineManager().delete("vm_from_another_app");
2016     }
2017 
2018     @Test
testVmDescriptorParcelUnparcel_noTrustedStorage()2019     public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
2020         assumeSupportedDevice();
2021 
2022         VirtualMachineConfig config =
2023                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2024                         .setDebugLevel(DEBUG_LEVEL_FULL)
2025                         .build();
2026 
2027         VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
2028         // Just start & stop the VM.
2029         runVmTestService(TAG, originalVm, (ts, tr) -> {});
2030 
2031         // Now create the descriptor and manually parcel & unparcel it.
2032         VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
2033 
2034         if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
2035             getVirtualMachineManager().delete("import_vm_from_unparceled");
2036         }
2037 
2038         VirtualMachine importVm =
2039                 getVirtualMachineManager()
2040                         .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
2041 
2042         assertFileContentsAreEqualInTwoVms(
2043                 "config.xml", "original_vm", "import_vm_from_unparceled");
2044         assertFileContentsAreEqualInTwoVms(
2045                 "instance.img", "original_vm", "import_vm_from_unparceled");
2046 
2047         // Check that we can start and stop imported vm as well
2048         runVmTestService(TAG, importVm, (ts, tr) -> {});
2049     }
2050 
2051     @Test
testVmDescriptorParcelUnparcel_withTrustedStorage()2052     public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
2053         assumeSupportedDevice();
2054 
2055         VirtualMachineConfig config =
2056                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2057                         .setDebugLevel(DEBUG_LEVEL_FULL)
2058                         .setEncryptedStorageBytes(1_000_000)
2059                         .build();
2060 
2061         VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
2062         // Just start & stop the VM.
2063         {
2064             TestResults testResults =
2065                     runVmTestService(
2066                             TAG,
2067                             originalVm,
2068                             (ts, tr) -> {
2069                                 ts.writeToFile("not a secret!", "/mnt/encryptedstore/secret.txt");
2070                             });
2071             assertThat(testResults.mException).isNull();
2072         }
2073 
2074         // Now create the descriptor and manually parcel & unparcel it.
2075         VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
2076 
2077         if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
2078             getVirtualMachineManager().delete("import_vm_from_unparceled");
2079         }
2080 
2081         VirtualMachine importVm =
2082                 getVirtualMachineManager()
2083                         .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
2084 
2085         assertFileContentsAreEqualInTwoVms(
2086                 "config.xml", "original_vm", "import_vm_from_unparceled");
2087         assertFileContentsAreEqualInTwoVms(
2088                 "instance.img", "original_vm", "import_vm_from_unparceled");
2089         assertFileContentsAreEqualInTwoVms(
2090                 "storage.img", "original_vm", "import_vm_from_unparceled");
2091 
2092         TestResults testResults =
2093                 runVmTestService(
2094                         TAG,
2095                         importVm,
2096                         (ts, tr) -> {
2097                             tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/secret.txt");
2098                         });
2099 
2100         assertThat(testResults.mException).isNull();
2101         assertThat(testResults.mFileContent).isEqualTo("not a secret!");
2102     }
2103 
2104     @Test
testShareVmWithAnotherApp()2105     public void testShareVmWithAnotherApp() throws Exception {
2106         assumeSupportedDevice();
2107 
2108         Context ctx = getContext();
2109         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
2110 
2111         VirtualMachineConfig config =
2112                 new VirtualMachineConfig.Builder(otherAppCtx)
2113                         .setDebugLevel(DEBUG_LEVEL_FULL)
2114                         .setProtectedVm(isProtectedVm())
2115                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
2116                         .setOs(os())
2117                         .build();
2118 
2119         VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
2120         // Just start & stop the VM.
2121         runVmTestService(TAG, vm, (ts, tr) -> {});
2122         // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
2123         VirtualMachineDescriptor vmDesc = vm.toDescriptor();
2124 
2125         Intent serviceIntent = new Intent();
2126         serviceIntent.setComponent(
2127                 new ComponentName(
2128                         VM_SHARE_APP_PACKAGE_NAME,
2129                         "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
2130         serviceIntent.setAction("com.android.microdroid.test.sharevm.VmShareService");
2131 
2132         VmShareServiceConnection connection = new VmShareServiceConnection();
2133         boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
2134         assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
2135 
2136         IVmShareTestService service = connection.waitForService();
2137         assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
2138 
2139         try {
2140             ITestService testServiceProxy = transferAndStartVm(service, vmDesc, "vm_to_share");
2141 
2142             int result = testServiceProxy.addInteger(37, 73);
2143             assertThat(result).isEqualTo(110);
2144         } finally {
2145             ctx.unbindService(connection);
2146         }
2147     }
2148 
2149     @Test
testShareVmWithAnotherApp_encryptedStorage()2150     public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
2151         assumeSupportedDevice();
2152 
2153         Context ctx = getContext();
2154         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
2155 
2156         VirtualMachineConfig config =
2157                 new VirtualMachineConfig.Builder(otherAppCtx)
2158                         .setDebugLevel(DEBUG_LEVEL_FULL)
2159                         .setProtectedVm(isProtectedVm())
2160                         .setEncryptedStorageBytes(3_000_000)
2161                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
2162                         .setOs(os())
2163                         .build();
2164 
2165         VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
2166         // Just start & stop the VM.
2167         runVmTestService(
2168                 TAG,
2169                 vm,
2170                 (ts, tr) -> {
2171                     ts.writeToFile(EXAMPLE_STRING, "/mnt/encryptedstore/private.key");
2172                 });
2173         // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
2174         VirtualMachineDescriptor vmDesc = vm.toDescriptor();
2175 
2176         Intent serviceIntent = new Intent();
2177         serviceIntent.setComponent(
2178                 new ComponentName(
2179                         VM_SHARE_APP_PACKAGE_NAME,
2180                         "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
2181         serviceIntent.setAction("com.android.microdroid.test.sharevm.VmShareService");
2182 
2183         VmShareServiceConnection connection = new VmShareServiceConnection();
2184         boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
2185         assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
2186 
2187         IVmShareTestService service = connection.waitForService();
2188         assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
2189 
2190         try {
2191             ITestService testServiceProxy = transferAndStartVm(service, vmDesc, "vm_to_share");
2192             String result = testServiceProxy.readFromFile("/mnt/encryptedstore/private.key");
2193             assertThat(result).isEqualTo(EXAMPLE_STRING);
2194         } finally {
2195             ctx.unbindService(connection);
2196         }
2197     }
2198 
transferAndStartVm( IVmShareTestService service, VirtualMachineDescriptor vmDesc, String vmName)2199     private ITestService transferAndStartVm(
2200             IVmShareTestService service, VirtualMachineDescriptor vmDesc, String vmName)
2201             throws Exception {
2202         // Send the VM descriptor to the other app. When received, it will reconstruct the VM
2203         // from the descriptor.
2204         service.importVm(vmDesc);
2205 
2206         // Now that the VM has been imported, we should be free to delete our copy (this is
2207         // what we recommend for VM transfer).
2208         getVirtualMachineManager().delete(vmName);
2209 
2210         // Ask the other app to start the imported VM, connect to the ITestService in it, create
2211         // a "proxy" ITestService binder that delegates all the calls to the VM, and share it
2212         // with this app. It will allow us to verify assertions on the running VM in the other
2213         // app.
2214         ITestService testServiceProxy = service.startVm();
2215         return testServiceProxy;
2216     }
2217 
2218     @Test
2219     @CddTest(requirements = {"9.17/C-1-5"})
testFileUnderBinHasExecutePermission()2220     public void testFileUnderBinHasExecutePermission() throws Exception {
2221         assumeSupportedDevice();
2222 
2223         VirtualMachineConfig vmConfig =
2224                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2225                         .setMemoryBytes(minMemoryRequired())
2226                         .setDebugLevel(DEBUG_LEVEL_FULL)
2227                         .build();
2228         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_perms", vmConfig);
2229 
2230         TestResults testResults =
2231                 runVmTestService(
2232                         TAG,
2233                         vm,
2234                         (ts, tr) -> {
2235                             tr.mFileMode = ts.getFilePermissions("/mnt/apk/bin/measure_io");
2236                         });
2237 
2238         testResults.assertNoException();
2239         int allPermissionsMask =
2240                 OsConstants.S_IRUSR
2241                         | OsConstants.S_IWUSR
2242                         | OsConstants.S_IXUSR
2243                         | OsConstants.S_IRGRP
2244                         | OsConstants.S_IWGRP
2245                         | OsConstants.S_IXGRP
2246                         | OsConstants.S_IROTH
2247                         | OsConstants.S_IWOTH
2248                         | OsConstants.S_IXOTH;
2249         int expectedPermissions = OsConstants.S_IRUSR | OsConstants.S_IXUSR;
2250         if (isFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT)) {
2251             expectedPermissions |= OsConstants.S_IRGRP | OsConstants.S_IXGRP;
2252         }
2253         assertThat(testResults.mFileMode & allPermissionsMask).isEqualTo(expectedPermissions);
2254     }
2255 
2256     // Taken from bionic/libc/kernel/uapi/linux/mount.h
2257     private static final int MS_RDONLY = 1;
2258     private static final int MS_NOEXEC = 8;
2259     private static final int MS_NOATIME = 1024;
2260 
2261     @Test
2262     @CddTest(requirements = {"9.17/C-1-5"})
dataIsMountedWithNoExec()2263     public void dataIsMountedWithNoExec() throws Exception {
2264         assumeSupportedDevice();
2265 
2266         VirtualMachineConfig vmConfig =
2267                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2268                         .setDebugLevel(DEBUG_LEVEL_FULL)
2269                         .build();
2270         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_data_mount", vmConfig);
2271 
2272         TestResults testResults =
2273                 runVmTestService(
2274                         TAG,
2275                         vm,
2276                         (ts, tr) -> {
2277                             tr.mMountFlags = ts.getMountFlags("/data");
2278                         });
2279 
2280         assertThat(testResults.mException).isNull();
2281         assertWithMessage("/data should be mounted with MS_NOEXEC")
2282                 .that(testResults.mMountFlags & MS_NOEXEC)
2283                 .isEqualTo(MS_NOEXEC);
2284     }
2285 
2286     @Test
2287     @CddTest(requirements = {"9.17/C-1-5"})
encryptedStoreIsMountedWithNoExec()2288     public void encryptedStoreIsMountedWithNoExec() throws Exception {
2289         assumeSupportedDevice();
2290 
2291         VirtualMachineConfig vmConfig =
2292                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2293                         .setDebugLevel(DEBUG_LEVEL_FULL)
2294                         .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
2295                         .build();
2296         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_encstore_no_exec", vmConfig);
2297 
2298         TestResults testResults =
2299                 runVmTestService(
2300                         TAG,
2301                         vm,
2302                         (ts, tr) -> {
2303                             tr.mMountFlags = ts.getMountFlags("/mnt/encryptedstore");
2304                         });
2305 
2306         assertThat(testResults.mException).isNull();
2307         assertWithMessage("/mnt/encryptedstore should be mounted with MS_NOEXEC")
2308                 .that(testResults.mMountFlags & MS_NOEXEC)
2309                 .isEqualTo(MS_NOEXEC);
2310     }
2311 
2312     @Test
2313     @VsrTest(requirements = {"VSR-7.1-001.003"})
kernelVersionRequirement()2314     public void kernelVersionRequirement() throws Exception {
2315         assumeVsrCompliant();
2316         int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
2317         assume().withMessage("Skip on devices launched before Android 14 (API level 34)")
2318                 .that(firstApiLevel)
2319                 .isAtLeast(34);
2320 
2321         String[] tokens = KERNEL_VERSION.split("\\.");
2322         int major = Integer.parseInt(tokens[0]);
2323         int minor = Integer.parseInt(tokens[1]);
2324 
2325         // Check kernel version >= 5.15
2326         assertTrue(major >= 5);
2327         if (major == 5) {
2328             assertTrue(minor >= 15);
2329         }
2330     }
2331 
2332     @Test
createAndRunRustVm()2333     public void createAndRunRustVm() throws Exception {
2334         // This test is here mostly to exercise the Rust wrapper around the VM Payload API.
2335         // We're testing the same functionality as in other tests, the only difference is
2336         // that the payload is written in Rust.
2337 
2338         assumeSupportedDevice();
2339 
2340         VirtualMachineConfig config =
2341                 newVmConfigBuilderWithPayloadBinary("libmicrodroid_testlib_rust.so")
2342                         .setMemoryBytes(minMemoryRequired())
2343                         .setDebugLevel(DEBUG_LEVEL_FULL)
2344                         .build();
2345         VirtualMachine vm = forceCreateNewVirtualMachine("rust_vm", config);
2346 
2347         TestResults testResults =
2348                 runVmTestService(
2349                         TAG,
2350                         vm,
2351                         (ts, tr) -> {
2352                             tr.mAddInteger = ts.addInteger(37, 73);
2353                             tr.mApkContentsPath = ts.getApkContentsPath();
2354                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
2355                             tr.mInstanceSecret = ts.insecurelyExposeVmInstanceSecret();
2356                         });
2357         testResults.assertNoException();
2358         assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
2359         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
2360         assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
2361         assertThat(testResults.mInstanceSecret).hasLength(32);
2362     }
2363 
2364     @Test
createAndRunRustVmWithEncryptedStorage()2365     public void createAndRunRustVmWithEncryptedStorage() throws Exception {
2366         // This test is here mostly to exercise the Rust wrapper around the VM Payload API.
2367         // We're testing the same functionality as in other tests, the only difference is
2368         // that the payload is written in Rust.
2369 
2370         assumeSupportedDevice();
2371 
2372         VirtualMachineConfig config =
2373                 newVmConfigBuilderWithPayloadBinary("libmicrodroid_testlib_rust.so")
2374                         .setMemoryBytes(minMemoryRequired())
2375                         .setDebugLevel(DEBUG_LEVEL_FULL)
2376                         .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
2377                         .build();
2378         VirtualMachine vm = forceCreateNewVirtualMachine("rust_vm", config);
2379 
2380         TestResults testResults =
2381                 runVmTestService(
2382                         TAG,
2383                         vm,
2384                         (ts, tr) -> tr.mEncryptedStoragePath = ts.getEncryptedStoragePath());
2385         testResults.assertNoException();
2386         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
2387     }
2388 
buildVmConfigWithVendor(File vendorDiskImage)2389     private VirtualMachineConfig buildVmConfigWithVendor(File vendorDiskImage) throws Exception {
2390         return buildVmConfigWithVendor(vendorDiskImage, "MicrodroidTestNativeLib.so");
2391     }
2392 
buildVmConfigWithVendor(File vendorDiskImage, String binaryPath)2393     private VirtualMachineConfig buildVmConfigWithVendor(File vendorDiskImage, String binaryPath)
2394             throws Exception {
2395         assumeSupportedDevice();
2396         // TODO(b/325094712): Boot fails with vendor partition in Cuttlefish.
2397         assumeFalse(
2398                 "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
2399                 isCuttlefish() || isGoldfish());
2400         // TODO(b/317567210): Boot fails with vendor partition in HWASAN enabled microdroid
2401         // after introducing verification based on DT and fstab in microdroid vendor partition.
2402         assumeFalse(
2403                 "boot with vendor partition is failing in HWASAN enabled Microdroid.", isHwasan());
2404         assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
2405         VirtualMachineConfig config =
2406                 newVmConfigBuilderWithPayloadBinary(binaryPath)
2407                         .setVendorDiskImage(vendorDiskImage)
2408                         .setDebugLevel(DEBUG_LEVEL_FULL)
2409                         .build();
2410         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
2411         return config;
2412     }
2413 
2414     @Test
configuringVendorDiskImageRequiresCustomPermission()2415     public void configuringVendorDiskImageRequiresCustomPermission() throws Exception {
2416         File vendorDiskImage =
2417                 new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
2418         VirtualMachineConfig config = buildVmConfigWithVendor(vendorDiskImage);
2419         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
2420 
2421         VirtualMachine vm =
2422                 forceCreateNewVirtualMachine("test_vendor_image_req_custom_permission", config);
2423         SecurityException e =
2424                 assertThrows(
2425                         SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
2426         assertThat(e)
2427                 .hasMessageThat()
2428                 .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
2429     }
2430 
2431     @Test
bootsWithVendorPartition()2432     public void bootsWithVendorPartition() throws Exception {
2433         File vendorDiskImage = new File("/vendor/etc/avf/microdroid/microdroid_vendor.img");
2434         assumeTrue("Microdroid vendor image doesn't exist, skip", vendorDiskImage.exists());
2435         VirtualMachineConfig config = buildVmConfigWithVendor(vendorDiskImage);
2436 
2437         VirtualMachine vm = forceCreateNewVirtualMachine("test_boot_with_vendor", config);
2438         TestResults testResults =
2439                 runVmTestService(
2440                         TAG,
2441                         vm,
2442                         (ts, tr) -> {
2443                             tr.mMountFlags = ts.getMountFlags("/vendor");
2444                         });
2445         assertThat(testResults.mException).isNull();
2446         int expectedFlags = MS_NOATIME | MS_RDONLY;
2447         assertThat(testResults.mMountFlags & expectedFlags).isEqualTo(expectedFlags);
2448     }
2449 
2450     @Test
bootsWithCustomVendorPartitionForNonPvm()2451     public void bootsWithCustomVendorPartitionForNonPvm() throws Exception {
2452         assumeNonProtectedVM();
2453         File vendorDiskImage =
2454                 new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
2455         VirtualMachineConfig config = buildVmConfigWithVendor(vendorDiskImage);
2456 
2457         VirtualMachine vm =
2458                 forceCreateNewVirtualMachine("test_boot_with_custom_vendor_non_pvm", config);
2459         TestResults testResults =
2460                 runVmTestService(
2461                         TAG,
2462                         vm,
2463                         (ts, tr) -> {
2464                             tr.mMountFlags = ts.getMountFlags("/vendor");
2465                         });
2466         assertThat(testResults.mException).isNull();
2467         int expectedFlags = MS_NOATIME | MS_RDONLY;
2468         assertThat(testResults.mMountFlags & expectedFlags).isEqualTo(expectedFlags);
2469     }
2470 
2471     @Test
bootFailsWithCustomVendorPartitionForPvm()2472     public void bootFailsWithCustomVendorPartitionForPvm() throws Exception {
2473         assumeProtectedVM();
2474         File vendorDiskImage =
2475                 new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
2476         VirtualMachineConfig config = buildVmConfigWithVendor(vendorDiskImage);
2477 
2478         BootResult bootResult = tryBootVmWithConfig(config, "test_boot_with_custom_vendor_pvm");
2479         assertThat(bootResult.payloadStarted).isFalse();
2480         assertThat(bootResult.deathReason).isEqualTo(VirtualMachineCallback.STOP_REASON_REBOOT);
2481     }
2482 
2483     @Test
creationFailsWithUnsignedVendorPartition()2484     public void creationFailsWithUnsignedVendorPartition() throws Exception {
2485         File vendorDiskImage =
2486                 new File(
2487                         "/data/local/tmp/cts/microdroid/test_microdroid_vendor_image_unsigned.img");
2488         VirtualMachineConfig config = buildVmConfigWithVendor(vendorDiskImage);
2489 
2490         VirtualMachine vm = forceCreateNewVirtualMachine("test_boot_with_unsigned_vendor", config);
2491         assertThrowsVmExceptionContaining(
2492                 () -> vm.run(), "Failed to extract vendor hashtree digest");
2493     }
2494 
2495     @Test
systemPartitionMountFlags()2496     public void systemPartitionMountFlags() throws Exception {
2497         assumeSupportedDevice();
2498 
2499         VirtualMachineConfig config =
2500                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2501                         .setDebugLevel(DEBUG_LEVEL_FULL)
2502                         .build();
2503 
2504         VirtualMachine vm = forceCreateNewVirtualMachine("test_system_mount_flags", config);
2505 
2506         TestResults testResults =
2507                 runVmTestService(
2508                         TAG,
2509                         vm,
2510                         (ts, tr) -> {
2511                             tr.mMountFlags = ts.getMountFlags("/");
2512                         });
2513 
2514         assertThat(testResults.mException).isNull();
2515         int expectedFlags = MS_NOATIME | MS_RDONLY;
2516         assertThat(testResults.mMountFlags & expectedFlags).isEqualTo(expectedFlags);
2517     }
2518 
2519     @Test
pageSize()2520     public void pageSize() throws Exception {
2521         assumeSupportedDevice();
2522 
2523         VirtualMachineConfig config =
2524                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
2525                         .setDebugLevel(DEBUG_LEVEL_FULL)
2526                         .build();
2527 
2528         VirtualMachine vm = forceCreateNewVirtualMachine("test_page_size", config);
2529 
2530         TestResults testResults =
2531                 runVmTestService(
2532                         TAG,
2533                         vm,
2534                         (ts, tr) -> {
2535                             tr.mPageSize = ts.getPageSize();
2536                         });
2537 
2538         assertThat(testResults.mException).isNull();
2539         int expectedPageSize = mOs.endsWith("_16k") ? 16384 : 4096;
2540         assertThat(testResults.mPageSize).isEqualTo(expectedPageSize);
2541     }
2542 
2543     private static class VmShareServiceConnection implements ServiceConnection {
2544 
2545         private final CountDownLatch mLatch = new CountDownLatch(1);
2546 
2547         private IVmShareTestService mVmShareTestService;
2548 
2549         @Override
onServiceConnected(ComponentName name, IBinder service)2550         public void onServiceConnected(ComponentName name, IBinder service) {
2551             mVmShareTestService = IVmShareTestService.Stub.asInterface(service);
2552             mLatch.countDown();
2553         }
2554 
2555         @Override
onServiceDisconnected(ComponentName name)2556         public void onServiceDisconnected(ComponentName name) {}
2557 
waitForService()2558         private IVmShareTestService waitForService() throws Exception {
2559             if (!mLatch.await(1, TimeUnit.MINUTES)) {
2560                 return null;
2561             }
2562             return mVmShareTestService;
2563         }
2564     }
2565 
2566     @Test
concurrentVms()2567     public void concurrentVms() throws Exception {
2568         final long vmSize = minMemoryRequired();
2569         final int numVMs = 8;
2570         final long availableMem = getAvailableMemory();
2571 
2572         // Let's not use more than half of the available memory
2573         assume().withMessage("Available memory (" + availableMem + " bytes) too small")
2574                 .that((numVMs * vmSize) <= (availableMem / 2))
2575                 .isTrue();
2576 
2577         VirtualMachine[] vms = new VirtualMachine[numVMs];
2578         try {
2579             for (int i = 0; i < numVMs; i++) {
2580                 VirtualMachineConfig config =
2581                         newVmConfigBuilderWithPayloadBinary("MicrodroidIdleNativeLib.so")
2582                                 .setDebugLevel(DEBUG_LEVEL_NONE)
2583                                 .setMemoryBytes(vmSize)
2584                                 .build();
2585 
2586                 vms[i] = forceCreateNewVirtualMachine("test_concurrent_vms_" + i, config);
2587                 vms[i].run();
2588             }
2589 
2590             for (VirtualMachine vm : vms) {
2591                 assertThat(vm.getStatus()).isEqualTo(VirtualMachine.STATUS_RUNNING);
2592             }
2593 
2594         } finally {
2595             // Ensure that VMs are all stopped. Otherwise we may try to reuse some of these for
2596             // another run of this test with different parameters.
2597             for (VirtualMachine vm : vms) {
2598                 if (vm != null) {
2599                     vm.close();
2600                 }
2601             }
2602         }
2603     }
2604 
getAvailableMemory()2605     private long getAvailableMemory() {
2606         ActivityManager am = getContext().getSystemService(ActivityManager.class);
2607         ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
2608         am.getMemoryInfo(memoryInfo);
2609         return memoryInfo.availMem;
2610     }
2611 
toParcelFromParcel(VirtualMachineDescriptor descriptor)2612     private VirtualMachineDescriptor toParcelFromParcel(VirtualMachineDescriptor descriptor) {
2613         Parcel parcel = Parcel.obtain();
2614         descriptor.writeToParcel(parcel, 0);
2615         parcel.setDataPosition(0);
2616         return VirtualMachineDescriptor.CREATOR.createFromParcel(parcel);
2617     }
2618 
assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)2619     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
2620             throws IOException {
2621         File file1 = getVmFile(vmName1, fileName);
2622         File file2 = getVmFile(vmName2, fileName);
2623         try (FileInputStream input1 = new FileInputStream(file1);
2624                 FileInputStream input2 = new FileInputStream(file2)) {
2625             assertThat(Arrays.equals(input1.readAllBytes(), input2.readAllBytes())).isTrue();
2626         }
2627     }
2628 
getVmFile(String vmName, String fileName)2629     private File getVmFile(String vmName, String fileName) {
2630         Context context = getContext();
2631         Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName, fileName);
2632         return filePath.toFile();
2633     }
2634 
assertThrowsVmException(ThrowingRunnable runnable)2635     private void assertThrowsVmException(ThrowingRunnable runnable) {
2636         assertThrows(VirtualMachineException.class, runnable);
2637     }
2638 
assertThrowsVmExceptionContaining( ThrowingRunnable runnable, String expectedContents)2639     private void assertThrowsVmExceptionContaining(
2640             ThrowingRunnable runnable, String expectedContents) {
2641         Exception e = assertThrows(VirtualMachineException.class, runnable);
2642         assertThat(e).hasMessageThat().contains(expectedContents);
2643     }
2644 
minMemoryRequired()2645     private long minMemoryRequired() {
2646         assertThat(Build.SUPPORTED_ABIS).isNotEmpty();
2647         String primaryAbi = Build.SUPPORTED_ABIS[0];
2648         switch (primaryAbi) {
2649             case "x86_64":
2650                 return MIN_MEM_X86_64;
2651             case "arm64-v8a":
2652             case "arm64-v8a-hwasan":
2653                 return MIN_MEM_ARM64;
2654         }
2655         throw new AssertionError("Unsupported ABI: " + primaryAbi);
2656     }
2657 }
2658