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