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