• 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.google.common.truth.Truth.assertThat;
29 import static com.google.common.truth.Truth.assertWithMessage;
30 import static com.google.common.truth.TruthJUnit.assume;
31 
32 import static org.junit.Assert.assertThrows;
33 import static org.junit.Assert.assertTrue;
34 import static org.junit.Assume.assumeTrue;
35 
36 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
37 
38 import android.app.Instrumentation;
39 import android.app.UiAutomation;
40 import android.content.ComponentName;
41 import android.content.Context;
42 import android.content.ContextWrapper;
43 import android.content.Intent;
44 import android.content.ServiceConnection;
45 import android.os.Build;
46 import android.os.IBinder;
47 import android.os.Parcel;
48 import android.os.ParcelFileDescriptor;
49 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
50 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
51 import android.os.SystemProperties;
52 import android.system.OsConstants;
53 import android.system.virtualmachine.VirtualMachine;
54 import android.system.virtualmachine.VirtualMachineCallback;
55 import android.system.virtualmachine.VirtualMachineConfig;
56 import android.system.virtualmachine.VirtualMachineDescriptor;
57 import android.system.virtualmachine.VirtualMachineException;
58 import android.system.virtualmachine.VirtualMachineManager;
59 
60 import androidx.test.platform.app.InstrumentationRegistry;
61 
62 import com.android.compatibility.common.util.CddTest;
63 import com.android.compatibility.common.util.VsrTest;
64 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
65 import com.android.microdroid.test.vmshare.IVmShareTestService;
66 import com.android.microdroid.testservice.IAppCallback;
67 import com.android.microdroid.testservice.ITestService;
68 import com.android.microdroid.testservice.IVmCallback;
69 
70 import com.google.common.base.Strings;
71 import com.google.common.truth.BooleanSubject;
72 
73 import org.junit.After;
74 import org.junit.Before;
75 import org.junit.Rule;
76 import org.junit.Test;
77 import org.junit.function.ThrowingRunnable;
78 import org.junit.rules.Timeout;
79 import org.junit.runner.RunWith;
80 import org.junit.runners.Parameterized;
81 
82 import java.io.BufferedReader;
83 import java.io.ByteArrayInputStream;
84 import java.io.File;
85 import java.io.FileInputStream;
86 import java.io.IOException;
87 import java.io.InputStream;
88 import java.io.InputStreamReader;
89 import java.io.OutputStream;
90 import java.io.OutputStreamWriter;
91 import java.io.RandomAccessFile;
92 import java.io.Writer;
93 import java.nio.file.Files;
94 import java.nio.file.Path;
95 import java.nio.file.Paths;
96 import java.time.LocalDateTime;
97 import java.time.format.DateTimeFormatter;
98 import java.util.Arrays;
99 import java.util.List;
100 import java.util.OptionalLong;
101 import java.util.UUID;
102 import java.util.concurrent.CompletableFuture;
103 import java.util.concurrent.CountDownLatch;
104 import java.util.concurrent.TimeUnit;
105 import java.util.concurrent.atomic.AtomicReference;
106 
107 import co.nstant.in.cbor.CborDecoder;
108 import co.nstant.in.cbor.model.Array;
109 import co.nstant.in.cbor.model.DataItem;
110 import co.nstant.in.cbor.model.MajorType;
111 
112 @RunWith(Parameterized.class)
113 public class MicrodroidTests extends MicrodroidDeviceTestBase {
114     private static final String TAG = "MicrodroidTests";
115 
116     @Rule public Timeout globalTimeout = Timeout.seconds(300);
117 
118     private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
119 
120     @Parameterized.Parameters(name = "protectedVm={0}")
protectedVmConfigs()121     public static Object[] protectedVmConfigs() {
122         return new Object[] { false, true };
123     }
124 
125     @Parameterized.Parameter public boolean mProtectedVm;
126 
127     @Before
setup()128     public void setup() {
129         grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
130         prepareTestSetup(mProtectedVm);
131     }
132 
133     @After
tearDown()134     public void tearDown() {
135         revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
136         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
137     }
138 
139     private static final long ONE_MEBI = 1024 * 1024;
140 
141     private static final long MIN_MEM_ARM64 = 150 * ONE_MEBI;
142     private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
143     private static final String EXAMPLE_STRING = "Literally any string!! :)";
144 
145     private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
146 
createAndConnectToVmHelper(int cpuTopology)147     private void createAndConnectToVmHelper(int cpuTopology) throws Exception {
148         assumeSupportedDevice();
149 
150         VirtualMachineConfig config =
151                 newVmConfigBuilder()
152                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
153                         .setMemoryBytes(minMemoryRequired())
154                         .setDebugLevel(DEBUG_LEVEL_FULL)
155                         .setCpuTopology(cpuTopology)
156                         .build();
157         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
158 
159         TestResults testResults =
160                 runVmTestService(
161                         TAG,
162                         vm,
163                         (ts, tr) -> {
164                             tr.mAddInteger = ts.addInteger(123, 456);
165                             tr.mAppRunProp = ts.readProperty("debug.microdroid.app.run");
166                             tr.mSublibRunProp = ts.readProperty("debug.microdroid.app.sublib.run");
167                             tr.mApkContentsPath = ts.getApkContentsPath();
168                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
169                         });
170         testResults.assertNoException();
171         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
172         assertThat(testResults.mAppRunProp).isEqualTo("true");
173         assertThat(testResults.mSublibRunProp).isEqualTo("true");
174         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
175         assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
176     }
177 
178     @Test
179     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndConnectToVm()180     public void createAndConnectToVm() throws Exception {
181         createAndConnectToVmHelper(CPU_TOPOLOGY_ONE_CPU);
182     }
183 
184     @Test
185     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndConnectToVm_HostCpuTopology()186     public void createAndConnectToVm_HostCpuTopology() throws Exception {
187         createAndConnectToVmHelper(CPU_TOPOLOGY_MATCH_HOST);
188     }
189 
190     @Test
191     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
createAndRunNoDebugVm()192     public void createAndRunNoDebugVm() throws Exception {
193         assumeSupportedDevice();
194 
195         // For most of our tests we use a debug VM so failures can be diagnosed.
196         // But we do need non-debug VMs to work, so run one.
197         VirtualMachineConfig config =
198                 newVmConfigBuilder()
199                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
200                         .setMemoryBytes(minMemoryRequired())
201                         .setDebugLevel(DEBUG_LEVEL_NONE)
202                         .setVmOutputCaptured(false)
203                         .build();
204         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
205 
206         TestResults testResults =
207                 runVmTestService(TAG, vm, (ts, tr) -> tr.mAddInteger = ts.addInteger(37, 73));
208         testResults.assertNoException();
209         assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
210     }
211 
212     @Test
213     @CddTest(
214             requirements = {
215                 "9.17/C-1-1",
216                 "9.17/C-1-2",
217                 "9.17/C-1-4",
218             })
createVmRequiresPermission()219     public void createVmRequiresPermission() {
220         assumeSupportedDevice();
221 
222         revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
223 
224         VirtualMachineConfig config =
225                 newVmConfigBuilder()
226                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
227                         .setMemoryBytes(minMemoryRequired())
228                         .build();
229 
230         SecurityException e =
231                 assertThrows(
232                         SecurityException.class,
233                         () -> forceCreateNewVirtualMachine("test_vm_requires_permission", config));
234         assertThat(e).hasMessageThat()
235                 .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
236     }
237 
238     @Test
239     @CddTest(requirements = {"9.17/C-1-1"})
autoCloseVm()240     public void autoCloseVm() throws Exception {
241         assumeSupportedDevice();
242 
243         VirtualMachineConfig config =
244                 newVmConfigBuilder()
245                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
246                         .setMemoryBytes(minMemoryRequired())
247                         .setDebugLevel(DEBUG_LEVEL_FULL)
248                         .build();
249 
250         try (VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config)) {
251             assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
252             // close() implicitly called on stopped VM.
253         }
254 
255         try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
256             vm.run();
257             assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
258             // close() implicitly called on running VM.
259         }
260 
261         try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
262             assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
263             getVirtualMachineManager().delete("test_vm");
264             assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
265             // close() implicitly called on deleted VM.
266         }
267     }
268 
269     @Test
270     @CddTest(requirements = {"9.17/C-1-1"})
autoCloseVmDescriptor()271     public void autoCloseVmDescriptor() throws Exception {
272         VirtualMachineConfig config =
273                 newVmConfigBuilder()
274                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
275                         .setDebugLevel(DEBUG_LEVEL_FULL)
276                         .build();
277         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
278         VirtualMachineDescriptor descriptor = vm.toDescriptor();
279 
280         Parcel parcel = Parcel.obtain();
281         try (descriptor) {
282             // It should be ok to use at this point
283             descriptor.writeToParcel(parcel, 0);
284         }
285 
286         // But not now - it's been closed.
287         assertThrows(IllegalStateException.class, () -> descriptor.writeToParcel(parcel, 0));
288         assertThrows(
289                 IllegalStateException.class,
290                 () -> getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor));
291 
292         // Closing again is fine.
293         descriptor.close();
294 
295         // Tidy up
296         parcel.recycle();
297     }
298 
299     @Test
300     @CddTest(requirements = {"9.17/C-1-1"})
vmDescriptorClosedOnImport()301     public void vmDescriptorClosedOnImport() throws Exception {
302         VirtualMachineConfig config =
303                 newVmConfigBuilder()
304                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
305                         .setDebugLevel(DEBUG_LEVEL_FULL)
306                         .build();
307         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
308         VirtualMachineDescriptor descriptor = vm.toDescriptor();
309 
310         getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor);
311         try {
312             // Descriptor has been implicitly closed
313             assertThrows(
314                     IllegalStateException.class,
315                     () ->
316                             getVirtualMachineManager()
317                                     .importFromDescriptor("imported_vm2", descriptor));
318         } finally {
319             getVirtualMachineManager().delete("imported_vm");
320         }
321     }
322 
323     @Test
324     @CddTest(requirements = {"9.17/C-1-1"})
vmLifecycleChecks()325     public void vmLifecycleChecks() throws Exception {
326         assumeSupportedDevice();
327 
328         VirtualMachineConfig config =
329                 newVmConfigBuilder()
330                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
331                         .setMemoryBytes(minMemoryRequired())
332                         .setDebugLevel(DEBUG_LEVEL_FULL)
333                         .build();
334 
335         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
336         assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
337 
338         // These methods require a running VM
339         assertThrowsVmExceptionContaining(
340                 () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "not in running state");
341         assertThrowsVmExceptionContaining(
342                 () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT),
343                 "not in running state");
344 
345         vm.run();
346         assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
347 
348         // These methods require a stopped VM
349         assertThrowsVmExceptionContaining(() -> vm.run(), "not in stopped state");
350         assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "not in stopped state");
351         assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "not in stopped state");
352         assertThrowsVmExceptionContaining(
353                 () -> getVirtualMachineManager().delete("test_vm"), "not in stopped state");
354 
355         vm.stop();
356         getVirtualMachineManager().delete("test_vm");
357         assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
358 
359         // None of these should work for a deleted VM
360         assertThrowsVmExceptionContaining(
361                 () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "deleted");
362         assertThrowsVmExceptionContaining(
363                 () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT), "deleted");
364         assertThrowsVmExceptionContaining(() -> vm.run(), "deleted");
365         assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "deleted");
366         assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "deleted");
367         // This is indistinguishable from the VM having never existed, so the message
368         // is non-specific.
369         assertThrowsVmException(() -> getVirtualMachineManager().delete("test_vm"));
370     }
371 
372     @Test
373     @CddTest(requirements = {"9.17/C-1-1"})
connectVsock()374     public void connectVsock() throws Exception {
375         assumeSupportedDevice();
376 
377         VirtualMachineConfig config =
378                 newVmConfigBuilder()
379                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
380                         .setMemoryBytes(minMemoryRequired())
381                         .setDebugLevel(DEBUG_LEVEL_FULL)
382                         .build();
383         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_vsock", config);
384 
385         AtomicReference<String> response = new AtomicReference<>();
386         String request = "Look not into the abyss";
387 
388         TestResults testResults =
389                 runVmTestService(
390                         TAG,
391                         vm,
392                         (service, results) -> {
393                             service.runEchoReverseServer();
394 
395                             ParcelFileDescriptor pfd =
396                                     vm.connectVsock(ITestService.ECHO_REVERSE_PORT);
397                             try (InputStream input = new AutoCloseInputStream(pfd);
398                                     OutputStream output = new AutoCloseOutputStream(pfd)) {
399                                 BufferedReader reader =
400                                         new BufferedReader(new InputStreamReader(input));
401                                 Writer writer = new OutputStreamWriter(output);
402                                 writer.write(request + "\n");
403                                 writer.flush();
404                                 response.set(reader.readLine());
405                             }
406                         });
407         testResults.assertNoException();
408         assertThat(response.get()).isEqualTo(new StringBuilder(request).reverse().toString());
409     }
410 
411     @Test
412     @CddTest(requirements = {"9.17/C-1-1"})
binderCallbacksWork()413     public void binderCallbacksWork() throws Exception {
414         assumeSupportedDevice();
415 
416         VirtualMachineConfig config =
417                 newVmConfigBuilder()
418                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
419                         .setMemoryBytes(minMemoryRequired())
420                         .setDebugLevel(DEBUG_LEVEL_FULL)
421                         .build();
422         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
423 
424         String request = "Hello";
425         CompletableFuture<String> response = new CompletableFuture<>();
426 
427         IAppCallback appCallback =
428                 new IAppCallback.Stub() {
429                     @Override
430                     public void setVmCallback(IVmCallback vmCallback) {
431                         // Do this on a separate thread to simulate an asynchronous trigger,
432                         // and to make sure it doesn't happen in the context of an inbound binder
433                         // call.
434                         new Thread() {
435                             @Override
436                             public void run() {
437                                 try {
438                                     vmCallback.echoMessage(request);
439                                 } catch (Exception e) {
440                                     response.completeExceptionally(e);
441                                 }
442                             }
443                         }.start();
444                     }
445 
446                     @Override
447                     public void onEchoRequestReceived(String message) {
448                         response.complete(message);
449                     }
450                 };
451 
452         TestResults testResults =
453                 runVmTestService(
454                         TAG,
455                         vm,
456                         (service, results) -> {
457                             service.requestCallback(appCallback);
458                             response.get(10, TimeUnit.SECONDS);
459                         });
460         testResults.assertNoException();
461         assertThat(response.getNow("no response")).isEqualTo("Received: " + request);
462     }
463 
464     @Test
465     @CddTest(requirements = {"9.17/C-1-1"})
vmConfigGetAndSetTests()466     public void vmConfigGetAndSetTests() {
467         // Minimal has as little as specified as possible; everything that can be is defaulted.
468         VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
469         VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryName("binary.so").build();
470 
471         assertThat(minimal.getApkPath()).isNull();
472         assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
473         assertThat(minimal.getMemoryBytes()).isEqualTo(0);
474         assertThat(minimal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_ONE_CPU);
475         assertThat(minimal.getPayloadBinaryName()).isEqualTo("binary.so");
476         assertThat(minimal.getPayloadConfigPath()).isNull();
477         assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
478         assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
479         assertThat(minimal.getEncryptedStorageBytes()).isEqualTo(0);
480         assertThat(minimal.isVmOutputCaptured()).isEqualTo(false);
481 
482         // Maximal has everything that can be set to some non-default value. (And has different
483         // values than minimal for the required fields.)
484         VirtualMachineConfig.Builder maximalBuilder =
485                 new VirtualMachineConfig.Builder(getContext())
486                         .setProtectedVm(mProtectedVm)
487                         .setPayloadConfigPath("config/path")
488                         .setApkPath("/apk/path")
489                         .setDebugLevel(DEBUG_LEVEL_FULL)
490                         .setMemoryBytes(42)
491                         .setCpuTopology(CPU_TOPOLOGY_MATCH_HOST)
492                         .setEncryptedStorageBytes(1_000_000)
493                         .setVmOutputCaptured(true);
494         VirtualMachineConfig maximal = maximalBuilder.build();
495 
496         assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
497         assertThat(maximal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_FULL);
498         assertThat(maximal.getMemoryBytes()).isEqualTo(42);
499         assertThat(maximal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_MATCH_HOST);
500         assertThat(maximal.getPayloadBinaryName()).isNull();
501         assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
502         assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
503         assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
504         assertThat(maximal.getEncryptedStorageBytes()).isEqualTo(1_000_000);
505         assertThat(maximal.isVmOutputCaptured()).isEqualTo(true);
506 
507         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
508         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
509         assertThat(maximal.isCompatibleWith(maximal)).isTrue();
510     }
511 
512     @Test
513     @CddTest(requirements = {"9.17/C-1-1"})
vmConfigBuilderValidationTests()514     public void vmConfigBuilderValidationTests() {
515         VirtualMachineConfig.Builder builder = newVmConfigBuilder();
516 
517         // All your null are belong to me.
518         assertThrows(NullPointerException.class, () -> new VirtualMachineConfig.Builder(null));
519         assertThrows(NullPointerException.class, () -> builder.setApkPath(null));
520         assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
521         assertThrows(NullPointerException.class, () -> builder.setPayloadBinaryName(null));
522         assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
523 
524         // Individual property checks.
525         assertThrows(
526                 IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
527         assertThrows(
528                 IllegalArgumentException.class, () -> builder.setPayloadBinaryName("dir/file.so"));
529         assertThrows(IllegalArgumentException.class, () -> builder.setDebugLevel(-1));
530         assertThrows(IllegalArgumentException.class, () -> builder.setMemoryBytes(0));
531         assertThrows(IllegalArgumentException.class, () -> builder.setCpuTopology(-1));
532         assertThrows(IllegalArgumentException.class, () -> builder.setEncryptedStorageBytes(0));
533 
534         // Consistency checks enforced at build time.
535         Exception e;
536         e = assertThrows(IllegalStateException.class, () -> builder.build());
537         assertThat(e).hasMessageThat().contains("setPayloadBinaryName must be called");
538 
539         VirtualMachineConfig.Builder protectedNotSet =
540                 new VirtualMachineConfig.Builder(getContext()).setPayloadBinaryName("binary.so");
541         e = assertThrows(IllegalStateException.class, () -> protectedNotSet.build());
542         assertThat(e).hasMessageThat().contains("setProtectedVm must be called");
543 
544         VirtualMachineConfig.Builder captureOutputOnNonDebuggable =
545                 newVmConfigBuilder()
546                         .setPayloadBinaryName("binary.so")
547                         .setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_NONE)
548                         .setVmOutputCaptured(true);
549         e = assertThrows(IllegalStateException.class, () -> captureOutputOnNonDebuggable.build());
550         assertThat(e).hasMessageThat().contains("debug level must be FULL to capture output");
551     }
552 
553     @Test
554     @CddTest(requirements = {"9.17/C-1-1"})
compatibleConfigTests()555     public void compatibleConfigTests() {
556         VirtualMachineConfig baseline = newBaselineBuilder().build();
557 
558         // A config must be compatible with itself
559         assertConfigCompatible(baseline, newBaselineBuilder()).isTrue();
560 
561         // Changes that must always be compatible
562         assertConfigCompatible(baseline, newBaselineBuilder().setMemoryBytes(99)).isTrue();
563         assertConfigCompatible(
564                         baseline, newBaselineBuilder().setCpuTopology(CPU_TOPOLOGY_MATCH_HOST))
565                 .isTrue();
566 
567         // Changes that must be incompatible, since they must change the VM identity.
568         assertConfigCompatible(baseline, newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL))
569                 .isFalse();
570         assertConfigCompatible(baseline, newBaselineBuilder().setPayloadBinaryName("different"))
571                 .isFalse();
572         int capabilities = getVirtualMachineManager().getCapabilities();
573         if ((capabilities & CAPABILITY_PROTECTED_VM) != 0
574                 && (capabilities & CAPABILITY_NON_PROTECTED_VM) != 0) {
575             assertConfigCompatible(baseline, newBaselineBuilder().setProtectedVm(!isProtectedVm()))
576                     .isFalse();
577         }
578 
579         // Changes that are currently incompatible for ease of implementation, but this might change
580         // in the future.
581         assertConfigCompatible(baseline, newBaselineBuilder().setApkPath("/different")).isFalse();
582         assertConfigCompatible(baseline, newBaselineBuilder().setEncryptedStorageBytes(100_000))
583                 .isFalse();
584 
585         VirtualMachineConfig.Builder debuggableBuilder =
586                 newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL);
587         VirtualMachineConfig debuggable = debuggableBuilder.build();
588         assertConfigCompatible(debuggable, debuggableBuilder.setVmOutputCaptured(true)).isFalse();
589 
590         VirtualMachineConfig currentContextConfig =
591                 new VirtualMachineConfig.Builder(getContext())
592                         .setProtectedVm(isProtectedVm())
593                         .setPayloadBinaryName("binary.so")
594                         .build();
595 
596         // packageName is not directly exposed by the config, so we have to be a bit creative
597         // to modify it.
598         Context otherContext =
599                 new ContextWrapper(getContext()) {
600                     @Override
601                     public String getPackageName() {
602                         return "other.package.name";
603                     }
604                 };
605         VirtualMachineConfig.Builder otherContextBuilder =
606                 new VirtualMachineConfig.Builder(otherContext)
607                         .setProtectedVm(isProtectedVm())
608                         .setPayloadBinaryName("binary.so");
609         assertConfigCompatible(currentContextConfig, otherContextBuilder).isFalse();
610     }
611 
newBaselineBuilder()612     private VirtualMachineConfig.Builder newBaselineBuilder() {
613         return newVmConfigBuilder().setPayloadBinaryName("binary.so").setApkPath("/apk/path");
614     }
615 
assertConfigCompatible( VirtualMachineConfig baseline, VirtualMachineConfig.Builder builder)616     private BooleanSubject assertConfigCompatible(
617             VirtualMachineConfig baseline, VirtualMachineConfig.Builder builder) {
618         return assertThat(builder.build().isCompatibleWith(baseline));
619     }
620 
621     @Test
622     @CddTest(requirements = {"9.17/C-1-1"})
vmUnitTests()623     public void vmUnitTests() throws Exception {
624         VirtualMachineConfig.Builder builder =
625                 newVmConfigBuilder().setPayloadBinaryName("binary.so");
626         VirtualMachineConfig config = builder.build();
627         VirtualMachine vm = forceCreateNewVirtualMachine("vm_name", config);
628 
629         assertThat(vm.getName()).isEqualTo("vm_name");
630         assertThat(vm.getConfig().getPayloadBinaryName()).isEqualTo("binary.so");
631         assertThat(vm.getConfig().getMemoryBytes()).isEqualTo(0);
632 
633         VirtualMachineConfig compatibleConfig = builder.setMemoryBytes(42).build();
634         vm.setConfig(compatibleConfig);
635 
636         assertThat(vm.getName()).isEqualTo("vm_name");
637         assertThat(vm.getConfig().getPayloadBinaryName()).isEqualTo("binary.so");
638         assertThat(vm.getConfig().getMemoryBytes()).isEqualTo(42);
639 
640         assertThat(getVirtualMachineManager().get("vm_name")).isSameInstanceAs(vm);
641     }
642 
643     @Test
644     @CddTest(requirements = {"9.17/C-1-1"})
testAvfRequiresUpdatableApex()645     public void testAvfRequiresUpdatableApex() throws Exception {
646         assertWithMessage("Devices that support AVF must also support updatable APEX")
647                 .that(SystemProperties.getBoolean("ro.apex.updatable", false))
648                 .isTrue();
649     }
650 
651     @Test
652     @CddTest(requirements = {"9.17/C-1-1"})
vmmGetAndCreate()653     public void vmmGetAndCreate() throws Exception {
654         assumeSupportedDevice();
655 
656         VirtualMachineConfig config =
657                 newVmConfigBuilder()
658                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
659                         .setMemoryBytes(minMemoryRequired())
660                         .setDebugLevel(DEBUG_LEVEL_FULL)
661                         .build();
662 
663         VirtualMachineManager vmm = getVirtualMachineManager();
664         String vmName = "vmName";
665 
666         try {
667             // VM does not yet exist
668             assertThat(vmm.get(vmName)).isNull();
669 
670             VirtualMachine vm1 = vmm.create(vmName, config);
671 
672             // Now it does, and we should get the same instance back
673             assertThat(vmm.get(vmName)).isSameInstanceAs(vm1);
674             assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm1);
675 
676             // Can't recreate it though
677             assertThrowsVmException(() -> vmm.create(vmName, config));
678 
679             vmm.delete(vmName);
680             assertThat(vmm.get(vmName)).isNull();
681 
682             // Now that we deleted the old one, this should create rather than get, and it should be
683             // a new instance.
684             VirtualMachine vm2 = vmm.getOrCreate(vmName, config);
685             assertThat(vm2).isNotSameInstanceAs(vm1);
686 
687             // The old one must remain deleted, or we'd have two VirtualMachine instances referring
688             // to the same VM.
689             assertThat(vm1.getStatus()).isEqualTo(STATUS_DELETED);
690 
691             // Subsequent gets should return this new one.
692             assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
693             assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
694         } finally {
695             vmm.delete(vmName);
696         }
697     }
698 
699     @Test
700     @CddTest(requirements = {"9.17/C-1-1"})
vmFilesStoredInDeDirWhenCreatedFromDEContext()701     public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
702         final Context ctx = getContext().createDeviceProtectedStorageContext();
703         final int userId = ctx.getUserId();
704         final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
705         VirtualMachineConfig config =
706                 newVmConfigBuilder().setPayloadBinaryName("binary.so").build();
707         try {
708             VirtualMachine vm = vmm.create("vm-name", config);
709             // TODO(b/261430346): what about non-primary user?
710             assertThat(vm.getRootDir().getAbsolutePath())
711                     .isEqualTo(
712                             "/data/user_de/" + userId + "/com.android.microdroid.test/vm/vm-name");
713         } finally {
714             vmm.delete("vm-name");
715         }
716     }
717 
718     @Test
719     @CddTest(requirements = {"9.17/C-1-1"})
vmFilesStoredInCeDirWhenCreatedFromCEContext()720     public void vmFilesStoredInCeDirWhenCreatedFromCEContext() throws Exception {
721         final Context ctx = getContext().createCredentialProtectedStorageContext();
722         final int userId = ctx.getUserId();
723         final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
724         VirtualMachineConfig config =
725                 newVmConfigBuilder().setPayloadBinaryName("binary.so").build();
726         try {
727             VirtualMachine vm = vmm.create("vm-name", config);
728             // TODO(b/261430346): what about non-primary user?
729             assertThat(vm.getRootDir().getAbsolutePath())
730                     .isEqualTo("/data/user/" + userId + "/com.android.microdroid.test/vm/vm-name");
731         } finally {
732             vmm.delete("vm-name");
733         }
734     }
735 
736     @Test
737     @CddTest(requirements = {"9.17/C-1-1"})
differentManagersForDifferentContexts()738     public void differentManagersForDifferentContexts() throws Exception {
739         final Context ceCtx = getContext().createCredentialProtectedStorageContext();
740         final Context deCtx = getContext().createDeviceProtectedStorageContext();
741         assertThat(ceCtx.getSystemService(VirtualMachineManager.class))
742                 .isNotSameInstanceAs(deCtx.getSystemService(VirtualMachineManager.class));
743     }
744 
745     @Test
746     @CddTest(requirements = {
747             "9.17/C-1-1",
748             "9.17/C-1-2",
749             "9.17/C-1-4",
750     })
createVmWithConfigRequiresPermission()751     public void createVmWithConfigRequiresPermission() throws Exception {
752         assumeSupportedDevice();
753 
754         VirtualMachineConfig config =
755                 newVmConfigBuilder()
756                         .setPayloadConfigPath("assets/vm_config.json")
757                         .setMemoryBytes(minMemoryRequired())
758                         .build();
759 
760         VirtualMachine vm =
761                 forceCreateNewVirtualMachine("test_vm_config_requires_permission", config);
762 
763         SecurityException e =
764                 assertThrows(
765                         SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
766         assertThat(e).hasMessageThat()
767                 .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
768     }
769 
770     @Test
771     @CddTest(requirements = {
772             "9.17/C-1-1",
773     })
deleteVm()774     public void deleteVm() throws Exception {
775         assumeSupportedDevice();
776 
777         VirtualMachineConfig config =
778                 newVmConfigBuilder()
779                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
780                         .setMemoryBytes(minMemoryRequired())
781                         .build();
782 
783         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
784         VirtualMachineManager vmm = getVirtualMachineManager();
785         vmm.delete("test_vm_delete");
786 
787         // VM should no longer exist
788         assertThat(vmm.get("test_vm_delete")).isNull();
789 
790         // Can't start the VM even with an existing reference
791         assertThrowsVmException(vm::run);
792 
793         // Can't delete the VM since it no longer exists
794         assertThrowsVmException(() -> vmm.delete("test_vm_delete"));
795     }
796 
797     @Test
798     @CddTest(
799             requirements = {
800                 "9.17/C-1-1",
801             })
deleteVmFiles()802     public void deleteVmFiles() throws Exception {
803         assumeSupportedDevice();
804 
805         VirtualMachineConfig config =
806                 newVmConfigBuilder()
807                         .setPayloadBinaryName("MicrodroidExitNativeLib.so")
808                         .setMemoryBytes(minMemoryRequired())
809                         .build();
810 
811         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
812         vm.run();
813         // If we explicitly stop a VM, that triggers some tidy up; so for this test we start a VM
814         // that immediately stops itself.
815         while (vm.getStatus() == STATUS_RUNNING) {
816             Thread.sleep(100);
817         }
818 
819         // Delete the files without telling VMM. This isn't a good idea, but we can't stop an
820         // app doing it, and we should recover from it.
821         for (File f : vm.getRootDir().listFiles()) {
822             Files.delete(f.toPath());
823         }
824         vm.getRootDir().delete();
825 
826         VirtualMachineManager vmm = getVirtualMachineManager();
827         assertThat(vmm.get("test_vm_delete")).isNull();
828         assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
829     }
830 
831     @Test
832     @CddTest(requirements = {
833             "9.17/C-1-1",
834     })
validApkPathIsAccepted()835     public void validApkPathIsAccepted() throws Exception {
836         assumeSupportedDevice();
837 
838         VirtualMachineConfig config =
839                 newVmConfigBuilder()
840                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
841                         .setApkPath(getContext().getPackageCodePath())
842                         .setMemoryBytes(minMemoryRequired())
843                         .setDebugLevel(DEBUG_LEVEL_FULL)
844                         .build();
845 
846         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_explicit_apk_path", config);
847 
848         TestResults testResults =
849                 runVmTestService(
850                         TAG,
851                         vm,
852                         (ts, tr) -> {
853                             tr.mApkContentsPath = ts.getApkContentsPath();
854                         });
855         testResults.assertNoException();
856         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
857     }
858 
859     @Test
860     @CddTest(requirements = {"9.17/C-1-1"})
invalidVmNameIsRejected()861     public void invalidVmNameIsRejected() {
862         VirtualMachineManager vmm = getVirtualMachineManager();
863         assertThrows(IllegalArgumentException.class, () -> vmm.get("../foo"));
864         assertThrows(IllegalArgumentException.class, () -> vmm.get(".."));
865     }
866 
867     @Test
868     @CddTest(requirements = {
869             "9.17/C-1-1",
870             "9.17/C-2-1"
871     })
extraApk()872     public void extraApk() throws Exception {
873         assumeSupportedDevice();
874 
875         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
876         VirtualMachineConfig config =
877                 newVmConfigBuilder()
878                         .setPayloadConfigPath("assets/vm_config_extra_apk.json")
879                         .setMemoryBytes(minMemoryRequired())
880                         .setDebugLevel(DEBUG_LEVEL_FULL)
881                         .build();
882         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_extra_apk", config);
883 
884         TestResults testResults =
885                 runVmTestService(
886                         TAG,
887                         vm,
888                         (ts, tr) -> {
889                             tr.mExtraApkTestProp =
890                                     ts.readProperty("debug.microdroid.test.extra_apk");
891                         });
892         assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
893     }
894 
895     @Test
bootFailsWhenLowMem()896     public void bootFailsWhenLowMem() throws Exception {
897         for (int memMib : new int[]{ 10, 20, 40 }) {
898             VirtualMachineConfig lowMemConfig =
899                     newVmConfigBuilder()
900                             .setPayloadBinaryName("MicrodroidTestNativeLib.so")
901                             .setMemoryBytes(memMib)
902                             .setDebugLevel(DEBUG_LEVEL_NONE)
903                             .setVmOutputCaptured(false)
904                             .build();
905             VirtualMachine vm = forceCreateNewVirtualMachine("low_mem", lowMemConfig);
906             final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
907             final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
908             VmEventListener listener =
909                     new VmEventListener() {
910                         @Override
911                         public void onPayloadReady(VirtualMachine vm) {
912                             onPayloadReadyExecuted.complete(true);
913                             super.onPayloadReady(vm);
914                         }
915                         @Override
916                         public void onStopped(VirtualMachine vm,  int reason) {
917                             onStoppedExecuted.complete(true);
918                             super.onStopped(vm, reason);
919                         }
920                     };
921             listener.runToFinish(TAG, vm);
922             // Assert that onStopped() was executed but onPayloadReady() was never run
923             assertThat(onStoppedExecuted.getNow(false)).isTrue();
924             assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
925         }
926     }
927 
928     @Test
929     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
changingNonDebuggableVmDebuggableInvalidatesVmIdentity()930     public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
931         changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
932     }
933 
934     @Test
935     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
changingDebuggableVmNonDebuggableInvalidatesVmIdentity()936     public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
937         changeDebugLevel(DEBUG_LEVEL_FULL, DEBUG_LEVEL_NONE);
938     }
939 
changeDebugLevel(int fromLevel, int toLevel)940     private void changeDebugLevel(int fromLevel, int toLevel) throws Exception {
941         assumeSupportedDevice();
942 
943         VirtualMachineConfig.Builder builder =
944                 newVmConfigBuilder()
945                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
946                         .setDebugLevel(fromLevel)
947                         .setVmOutputCaptured(false);
948         VirtualMachineConfig normalConfig = builder.build();
949         forceCreateNewVirtualMachine("test_vm", normalConfig);
950         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
951 
952         // Try to run the VM again with the previous instance.img
953         // We need to make sure that no changes on config don't invalidate the identity, to compare
954         // the result with the below "different debug level" test.
955         File vmInstance = getVmFile("test_vm", "instance.img");
956         File vmInstanceBackup = File.createTempFile("instance", ".img");
957         Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
958         forceCreateNewVirtualMachine("test_vm", normalConfig);
959         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
960         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
961 
962         // Launch the same VM with a different debug level. The Java API prohibits this
963         // (thankfully).
964         // For testing, we do that by creating a new VM with debug level, and copy the old instance
965         // image to the new VM instance image.
966         VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
967         forceCreateNewVirtualMachine("test_vm", debugConfig);
968         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
969         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
970     }
971 
972     private static class VmCdis {
973         public byte[] cdiAttest;
974         public byte[] instanceSecret;
975     }
976 
launchVmAndGetCdis(String instanceName)977     private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
978         VirtualMachine vm = getVirtualMachineManager().get(instanceName);
979         VmCdis vmCdis = new VmCdis();
980         CompletableFuture<Exception> exception = new CompletableFuture<>();
981         VmEventListener listener =
982                 new VmEventListener() {
983                     @Override
984                     public void onPayloadReady(VirtualMachine vm) {
985                         try {
986                             ITestService testService =
987                                     ITestService.Stub.asInterface(
988                                             vm.connectToVsockServer(ITestService.SERVICE_PORT));
989                             vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi();
990                             vmCdis.instanceSecret = testService.insecurelyExposeVmInstanceSecret();
991                         } catch (Exception e) {
992                             exception.complete(e);
993                         } finally {
994                             forceStop(vm);
995                         }
996                     }
997                 };
998         listener.runToFinish(TAG, vm);
999         Exception e = exception.getNow(null);
1000         if (e != null) {
1001             throw new RuntimeException(e);
1002         }
1003         return vmCdis;
1004     }
1005 
1006     @Test
1007     @CddTest(requirements = {
1008             "9.17/C-1-1",
1009             "9.17/C-2-7"
1010     })
instancesOfSameVmHaveDifferentCdis()1011     public void instancesOfSameVmHaveDifferentCdis() throws Exception {
1012         assumeSupportedDevice();
1013 
1014         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1015         VirtualMachineConfig normalConfig =
1016                 newVmConfigBuilder()
1017                         .setPayloadConfigPath("assets/vm_config.json")
1018                         .setDebugLevel(DEBUG_LEVEL_FULL)
1019                         .build();
1020         forceCreateNewVirtualMachine("test_vm_a", normalConfig);
1021         forceCreateNewVirtualMachine("test_vm_b", normalConfig);
1022         VmCdis vm_a_cdis = launchVmAndGetCdis("test_vm_a");
1023         VmCdis vm_b_cdis = launchVmAndGetCdis("test_vm_b");
1024         assertThat(vm_a_cdis.cdiAttest).isNotNull();
1025         assertThat(vm_b_cdis.cdiAttest).isNotNull();
1026         assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiAttest);
1027         assertThat(vm_a_cdis.instanceSecret).isNotNull();
1028         assertThat(vm_b_cdis.instanceSecret).isNotNull();
1029         assertThat(vm_a_cdis.instanceSecret).isNotEqualTo(vm_b_cdis.instanceSecret);
1030     }
1031 
1032     @Test
1033     @CddTest(requirements = {
1034             "9.17/C-1-1",
1035             "9.17/C-2-7"
1036     })
sameInstanceKeepsSameCdis()1037     public void sameInstanceKeepsSameCdis() throws Exception {
1038         assumeSupportedDevice();
1039         assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
1040 
1041         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1042         VirtualMachineConfig normalConfig =
1043                 newVmConfigBuilder()
1044                         .setPayloadConfigPath("assets/vm_config.json")
1045                         .setDebugLevel(DEBUG_LEVEL_FULL)
1046                         .build();
1047         forceCreateNewVirtualMachine("test_vm", normalConfig);
1048 
1049         VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
1050         VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
1051         // The attestation CDI isn't specified to be stable, though it might be
1052         assertThat(first_boot_cdis.instanceSecret).isNotNull();
1053         assertThat(second_boot_cdis.instanceSecret).isNotNull();
1054         assertThat(first_boot_cdis.instanceSecret).isEqualTo(second_boot_cdis.instanceSecret);
1055     }
1056 
1057     @Test
1058     @CddTest(requirements = {
1059             "9.17/C-1-1",
1060             "9.17/C-2-7"
1061     })
bccIsSuperficiallyWellFormed()1062     public void bccIsSuperficiallyWellFormed() throws Exception {
1063         assumeSupportedDevice();
1064 
1065         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1066         VirtualMachineConfig normalConfig =
1067                 newVmConfigBuilder()
1068                         .setPayloadConfigPath("assets/vm_config.json")
1069                         .setDebugLevel(DEBUG_LEVEL_FULL)
1070                         .build();
1071         VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm", normalConfig);
1072         TestResults testResults =
1073                 runVmTestService(
1074                         TAG,
1075                         vm,
1076                         (service, results) -> {
1077                             results.mBcc = service.getBcc();
1078                         });
1079         testResults.assertNoException();
1080         byte[] bccBytes = testResults.mBcc;
1081         assertThat(bccBytes).isNotNull();
1082 
1083         ByteArrayInputStream bais = new ByteArrayInputStream(bccBytes);
1084         List<DataItem> dataItems = new CborDecoder(bais).decode();
1085         assertThat(dataItems.size()).isEqualTo(1);
1086         assertThat(dataItems.get(0).getMajorType()).isEqualTo(MajorType.ARRAY);
1087         List<DataItem> rootArrayItems = ((Array) dataItems.get(0)).getDataItems();
1088         assertThat(rootArrayItems.size()).isAtLeast(2); // Public key and one certificate
1089         if (mProtectedVm) {
1090             // pvmfw truncates the DICE chain it gets, so we expect exactly entries for: public key,
1091             // Microdroid and app payload.
1092             assertThat(rootArrayItems.size()).isEqualTo(3);
1093         }
1094     }
1095 
1096     @Test
1097     @CddTest(requirements = {
1098             "9.17/C-1-1",
1099             "9.17/C-1-2"
1100     })
accessToCdisIsRestricted()1101     public void accessToCdisIsRestricted() throws Exception {
1102         assumeSupportedDevice();
1103 
1104         VirtualMachineConfig config =
1105                 newVmConfigBuilder()
1106                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1107                         .setDebugLevel(DEBUG_LEVEL_FULL)
1108                         .build();
1109         forceCreateNewVirtualMachine("test_vm", config);
1110 
1111         assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
1112     }
1113 
1114 
1115     private static final UUID MICRODROID_PARTITION_UUID =
1116             UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
1117     private static final UUID PVM_FW_PARTITION_UUID =
1118             UUID.fromString("90d2174a-038a-4bc6-adf3-824848fc5825");
1119     private static final long BLOCK_SIZE = 512;
1120 
1121     // Find the starting offset which holds the data of a partition having UUID.
1122     // This is a kind of hack; rather than parsing QCOW2 we exploit the fact that the cluster size
1123     // is normally greater than 512. It implies that the partition data should exist at a block
1124     // which follows the header block
findPartitionDataOffset(RandomAccessFile file, UUID uuid)1125     private OptionalLong findPartitionDataOffset(RandomAccessFile file, UUID uuid)
1126             throws IOException {
1127         // For each 512-byte block in file, check header
1128         long fileSize = file.length();
1129 
1130         for (long idx = 0; idx + BLOCK_SIZE < fileSize; idx += BLOCK_SIZE) {
1131             file.seek(idx);
1132             long high = file.readLong();
1133             long low = file.readLong();
1134             if (uuid.equals(new UUID(high, low))) return OptionalLong.of(idx + BLOCK_SIZE);
1135         }
1136         return OptionalLong.empty();
1137     }
1138 
flipBit(RandomAccessFile file, long offset)1139     private void flipBit(RandomAccessFile file, long offset) throws IOException {
1140         file.seek(offset);
1141         int b = file.readByte();
1142         file.seek(offset);
1143         file.writeByte(b ^ 1);
1144     }
1145 
prepareInstanceImage(String vmName)1146     private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
1147         VirtualMachineConfig config =
1148                 newVmConfigBuilder()
1149                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1150                         .setDebugLevel(DEBUG_LEVEL_FULL)
1151                         .build();
1152 
1153         forceCreateNewVirtualMachine(vmName, config);
1154         assertThat(tryBootVm(TAG, vmName).payloadStarted).isTrue();
1155         File instanceImgPath = getVmFile(vmName, "instance.img");
1156         return new RandomAccessFile(instanceImgPath, "rw");
1157     }
1158 
assertThatPartitionIsMissing(UUID partitionUuid)1159     private void assertThatPartitionIsMissing(UUID partitionUuid) throws Exception {
1160         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
1161         assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent())
1162                 .isFalse();
1163     }
1164 
1165     // Flips a bit of given partition, and then see if boot fails.
assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)1166     private void assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)
1167             throws Exception {
1168         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
1169         OptionalLong offset = findPartitionDataOffset(instanceFile, partitionUuid);
1170         assertThat(offset.isPresent()).isTrue();
1171 
1172         flipBit(instanceFile, offset.getAsLong());
1173 
1174         BootResult result = tryBootVm(TAG, "test_vm_integrity");
1175         assertThat(result.payloadStarted).isFalse();
1176 
1177         // This failure should shut the VM down immediately and shouldn't trigger a hangup.
1178         assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.STOP_REASON_HANGUP);
1179     }
1180 
1181     @Test
1182     @CddTest(requirements = {
1183             "9.17/C-1-1",
1184             "9.17/C-2-7"
1185     })
bootFailsWhenMicrodroidDataIsCompromised()1186     public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
1187         assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
1188     }
1189 
1190     @Test
1191     @CddTest(requirements = {
1192             "9.17/C-1-1",
1193             "9.17/C-2-7"
1194     })
bootFailsWhenPvmFwDataIsCompromised()1195     public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
1196         if (mProtectedVm) {
1197             assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID);
1198         } else {
1199             // non-protected VM shouldn't have pvmfw data
1200             assertThatPartitionIsMissing(PVM_FW_PARTITION_UUID);
1201         }
1202     }
1203 
1204     @Test
bootFailsWhenConfigIsInvalid()1205     public void bootFailsWhenConfigIsInvalid() throws Exception {
1206         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1207         VirtualMachineConfig normalConfig =
1208                 newVmConfigBuilder()
1209                         .setPayloadConfigPath("assets/vm_config_no_task.json")
1210                         .setDebugLevel(DEBUG_LEVEL_FULL)
1211                         .build();
1212         forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
1213 
1214         BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_config");
1215         assertThat(bootResult.payloadStarted).isFalse();
1216         assertThat(bootResult.deathReason).isEqualTo(
1217                 VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
1218     }
1219 
1220     @Test
bootFailsWhenBinaryNameIsInvalid()1221     public void bootFailsWhenBinaryNameIsInvalid() throws Exception {
1222         VirtualMachineConfig.Builder builder =
1223                 newVmConfigBuilder().setPayloadBinaryName("DoesNotExist.so");
1224         VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
1225         forceCreateNewVirtualMachine("test_vm_invalid_binary_path", normalConfig);
1226 
1227         BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_binary_path");
1228         assertThat(bootResult.payloadStarted).isFalse();
1229         assertThat(bootResult.deathReason).isEqualTo(
1230                 VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR);
1231     }
1232 
1233     // Checks whether microdroid_launcher started but payload failed. reason must be recorded in the
1234     // console output.
assertThatPayloadFailsDueTo(VirtualMachine vm, String reason)1235     private void assertThatPayloadFailsDueTo(VirtualMachine vm, String reason) throws Exception {
1236         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
1237         final CompletableFuture<Integer> exitCodeFuture = new CompletableFuture<>();
1238         VmEventListener listener =
1239                 new VmEventListener() {
1240                     @Override
1241                     public void onPayloadStarted(VirtualMachine vm) {
1242                         payloadStarted.complete(true);
1243                     }
1244 
1245                     @Override
1246                     public void onPayloadFinished(VirtualMachine vm, int exitCode) {
1247                         exitCodeFuture.complete(exitCode);
1248                     }
1249                 };
1250         listener.runToFinish(TAG, vm);
1251 
1252         assertThat(payloadStarted.getNow(false)).isTrue();
1253         assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
1254         assertThat(listener.getConsoleOutput()).contains(reason);
1255     }
1256 
1257     @Test
bootFailsWhenBinaryIsMissingEntryFunction()1258     public void bootFailsWhenBinaryIsMissingEntryFunction() throws Exception {
1259         VirtualMachineConfig normalConfig =
1260                 newVmConfigBuilder()
1261                         .setPayloadBinaryName("MicrodroidEmptyNativeLib.so")
1262                         .setDebugLevel(DEBUG_LEVEL_FULL)
1263                         .setVmOutputCaptured(true)
1264                         .build();
1265         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_missing_entry", normalConfig);
1266 
1267         assertThatPayloadFailsDueTo(vm, "Failed to find entrypoint");
1268     }
1269 
1270     @Test
bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs()1271     public void bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs() throws Exception {
1272         VirtualMachineConfig normalConfig =
1273                 newVmConfigBuilder()
1274                         .setPayloadBinaryName("MicrodroidPrivateLinkingNativeLib.so")
1275                         .setDebugLevel(DEBUG_LEVEL_FULL)
1276                         .setVmOutputCaptured(true)
1277                         .build();
1278         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_private_linking", normalConfig);
1279 
1280         assertThatPayloadFailsDueTo(vm, "Failed to dlopen");
1281     }
1282 
1283     @Test
sameInstancesShareTheSameVmObject()1284     public void sameInstancesShareTheSameVmObject() throws Exception {
1285         VirtualMachineConfig config =
1286                 newVmConfigBuilder().setPayloadBinaryName("MicrodroidTestNativeLib.so").build();
1287 
1288         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1289         VirtualMachine vm2 = getVirtualMachineManager().get("test_vm");
1290         assertThat(vm).isEqualTo(vm2);
1291 
1292         VirtualMachine newVm = forceCreateNewVirtualMachine("test_vm", config);
1293         VirtualMachine newVm2 = getVirtualMachineManager().get("test_vm");
1294         assertThat(newVm).isEqualTo(newVm2);
1295 
1296         assertThat(vm).isNotEqualTo(newVm);
1297     }
1298 
1299     @Test
importedVmAndOriginalVmHaveTheSameCdi()1300     public void importedVmAndOriginalVmHaveTheSameCdi() throws Exception {
1301         assumeSupportedDevice();
1302         // Arrange
1303         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
1304         VirtualMachineConfig config =
1305                 newVmConfigBuilder()
1306                         .setPayloadConfigPath("assets/vm_config.json")
1307                         .setDebugLevel(DEBUG_LEVEL_FULL)
1308                         .build();
1309         String vmNameOrig = "test_vm_orig";
1310         String vmNameImport = "test_vm_import";
1311         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
1312         VmCdis origCdis = launchVmAndGetCdis(vmNameOrig);
1313         assertThat(origCdis.instanceSecret).isNotNull();
1314         VirtualMachineManager vmm = getVirtualMachineManager();
1315         if (vmm.get(vmNameImport) != null) {
1316             vmm.delete(vmNameImport);
1317         }
1318 
1319         // Action
1320         // The imported VM will be fetched by name later.
1321         vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
1322 
1323         // Asserts
1324         VmCdis importCdis = launchVmAndGetCdis(vmNameImport);
1325         assertThat(origCdis.instanceSecret).isEqualTo(importCdis.instanceSecret);
1326     }
1327 
1328     @Test
1329     @CddTest(requirements = {"9.17/C-1-1"})
importedVmIsEqualToTheOriginalVm_WithoutStorage()1330     public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
1331         TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
1332         assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
1333     }
1334 
1335     @Test
1336     @CddTest(requirements = {"9.17/C-1-1"})
importedVmIsEqualToTheOriginalVm_WithStorage()1337     public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
1338         TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
1339         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
1340     }
1341 
importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)1342     private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
1343             throws Exception {
1344         // Arrange
1345         VirtualMachineConfig.Builder builder =
1346                 newVmConfigBuilder()
1347                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1348                         .setDebugLevel(DEBUG_LEVEL_FULL);
1349         if (encryptedStoreEnabled) {
1350             builder.setEncryptedStorageBytes(4_000_000);
1351         }
1352         VirtualMachineConfig config = builder.build();
1353         String vmNameOrig = "test_vm_orig";
1354         String vmNameImport = "test_vm_import";
1355         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
1356         // Run something to make the instance.img different with the initialized one.
1357         TestResults origTestResults =
1358                 runVmTestService(
1359                         TAG,
1360                         vmOrig,
1361                         (ts, tr) -> {
1362                             tr.mAddInteger = ts.addInteger(123, 456);
1363                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1364                         });
1365         origTestResults.assertNoException();
1366         assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
1367         VirtualMachineManager vmm = getVirtualMachineManager();
1368         if (vmm.get(vmNameImport) != null) {
1369             vmm.delete(vmNameImport);
1370         }
1371 
1372         // Action
1373         VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
1374 
1375         // Asserts
1376         assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
1377         assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
1378         if (encryptedStoreEnabled) {
1379             assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
1380         }
1381         assertThat(vmImport).isNotEqualTo(vmOrig);
1382         vmm.delete(vmNameOrig);
1383         assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
1384         TestResults testResults =
1385                 runVmTestService(
1386                         TAG,
1387                         vmImport,
1388                         (ts, tr) -> {
1389                             tr.mAddInteger = ts.addInteger(123, 456);
1390                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1391                         });
1392         testResults.assertNoException();
1393         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
1394         return testResults;
1395     }
1396 
1397     @Test
1398     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageAvailable()1399     public void encryptedStorageAvailable() throws Exception {
1400         assumeSupportedDevice();
1401 
1402         VirtualMachineConfig config =
1403                 newVmConfigBuilder()
1404                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1405                         .setMemoryBytes(minMemoryRequired())
1406                         .setEncryptedStorageBytes(4_000_000)
1407                         .setDebugLevel(DEBUG_LEVEL_FULL)
1408                         .build();
1409         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1410 
1411         TestResults testResults =
1412                 runVmTestService(
1413                         TAG,
1414                         vm,
1415                         (ts, tr) -> {
1416                             tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
1417                         });
1418         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
1419     }
1420 
1421     @Test
1422     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageIsInaccessibleToDifferentVm()1423     public void encryptedStorageIsInaccessibleToDifferentVm() throws Exception {
1424         assumeSupportedDevice();
1425 
1426         VirtualMachineConfig config =
1427                 newVmConfigBuilder()
1428                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1429                         .setMemoryBytes(minMemoryRequired())
1430                         .setEncryptedStorageBytes(4_000_000)
1431                         .setDebugLevel(DEBUG_LEVEL_FULL)
1432                         .build();
1433 
1434         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
1435 
1436         TestResults testResults =
1437                 runVmTestService(
1438                         TAG,
1439                         vm,
1440                         (ts, tr) -> {
1441                             ts.writeToFile(
1442                                     /* content= */ EXAMPLE_STRING,
1443                                     /* path= */ "/mnt/encryptedstore/test_file");
1444                         });
1445         testResults.assertNoException();
1446 
1447         // Start a different vm (this changes the vm identity)
1448         VirtualMachine diff_test_vm = forceCreateNewVirtualMachine("diff_test_vm", config);
1449 
1450         // Replace the backing storage image to the original one
1451         File storageImgOrig = getVmFile("test_vm", "storage.img");
1452         File storageImgNew = getVmFile("diff_test_vm", "storage.img");
1453         Files.copy(storageImgOrig.toPath(), storageImgNew.toPath(), REPLACE_EXISTING);
1454         assertFileContentsAreEqualInTwoVms("storage.img", "test_vm", "diff_test_vm");
1455 
1456         CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
1457         CompletableFuture<Boolean> onErrorExecuted = new CompletableFuture<>();
1458         CompletableFuture<String> errorMessage = new CompletableFuture<>();
1459         VmEventListener listener =
1460                 new VmEventListener() {
1461                     @Override
1462                     public void onPayloadReady(VirtualMachine vm) {
1463                         onPayloadReadyExecuted.complete(true);
1464                         super.onPayloadReady(vm);
1465                     }
1466 
1467                     @Override
1468                     public void onError(VirtualMachine vm, int errorCode, String message) {
1469                         onErrorExecuted.complete(true);
1470                         errorMessage.complete(message);
1471                         super.onError(vm, errorCode, message);
1472                     }
1473                 };
1474         listener.runToFinish(TAG, diff_test_vm);
1475 
1476         // Assert that payload never started & error message reflects storage error.
1477         assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
1478         assertThat(onErrorExecuted.getNow(false)).isTrue();
1479         assertThat(errorMessage.getNow("")).contains("Unable to prepare encrypted storage");
1480     }
1481 
1482     @Test
1483     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
microdroidLauncherHasEmptyCapabilities()1484     public void microdroidLauncherHasEmptyCapabilities() throws Exception {
1485         assumeSupportedDevice();
1486 
1487         final VirtualMachineConfig vmConfig =
1488                 newVmConfigBuilder()
1489                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1490                         .setMemoryBytes(minMemoryRequired())
1491                         .setDebugLevel(DEBUG_LEVEL_FULL)
1492                         .build();
1493         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_caps", vmConfig);
1494 
1495         final TestResults testResults =
1496                 runVmTestService(
1497                         TAG,
1498                         vm,
1499                         (ts, tr) -> {
1500                             tr.mEffectiveCapabilities = ts.getEffectiveCapabilities();
1501                         });
1502 
1503         testResults.assertNoException();
1504         assertThat(testResults.mEffectiveCapabilities).isEmpty();
1505     }
1506 
1507     @Test
1508     @CddTest(requirements = {"9.17/C-1-1"})
encryptedStorageIsPersistent()1509     public void encryptedStorageIsPersistent() throws Exception {
1510         assumeSupportedDevice();
1511 
1512         VirtualMachineConfig config =
1513                 newVmConfigBuilder()
1514                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1515                         .setMemoryBytes(minMemoryRequired())
1516                         .setEncryptedStorageBytes(4_000_000)
1517                         .setDebugLevel(DEBUG_LEVEL_FULL)
1518                         .build();
1519         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_a", config);
1520         TestResults testResults =
1521                 runVmTestService(
1522                         TAG,
1523                         vm,
1524                         (ts, tr) -> {
1525                             ts.writeToFile(
1526                                     /* content= */ EXAMPLE_STRING,
1527                                     /* path= */ "/mnt/encryptedstore/test_file");
1528                         });
1529         testResults.assertNoException();
1530 
1531         // Re-run the same VM & verify the file persisted. Note, the previous `runVmTestService`
1532         // stopped the VM
1533         testResults =
1534                 runVmTestService(
1535                         TAG,
1536                         vm,
1537                         (ts, tr) -> {
1538                             tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
1539                         });
1540         testResults.assertNoException();
1541         assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
1542     }
1543 
1544     @Test
1545     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
canReadFileFromAssets_debugFull()1546     public void canReadFileFromAssets_debugFull() throws Exception {
1547         assumeSupportedDevice();
1548 
1549         VirtualMachineConfig config =
1550                 newVmConfigBuilder()
1551                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1552                         .setMemoryBytes(minMemoryRequired())
1553                         .setDebugLevel(DEBUG_LEVEL_FULL)
1554                         .build();
1555         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_read_from_assets", config);
1556 
1557         TestResults testResults =
1558                 runVmTestService(
1559                         TAG,
1560                         vm,
1561                         (testService, ts) -> {
1562                             ts.mFileContent = testService.readFromFile("/mnt/apk/assets/file.txt");
1563                         });
1564 
1565         testResults.assertNoException();
1566         assertThat(testResults.mFileContent).isEqualTo("Hello, I am a file!");
1567     }
1568 
1569     @Test
outputShouldBeExplicitlyCaptured()1570     public void outputShouldBeExplicitlyCaptured() throws Exception {
1571         assumeSupportedDevice();
1572 
1573         final VirtualMachineConfig vmConfig =
1574                 new VirtualMachineConfig.Builder(getContext())
1575                         .setProtectedVm(mProtectedVm)
1576                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1577                         .setDebugLevel(DEBUG_LEVEL_FULL)
1578                         .build();
1579         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_forward_log", vmConfig);
1580         vm.run();
1581 
1582         try {
1583             assertThrowsVmExceptionContaining(
1584                     () -> vm.getConsoleOutput(), "Capturing vm outputs is turned off");
1585             assertThrowsVmExceptionContaining(
1586                     () -> vm.getLogOutput(), "Capturing vm outputs is turned off");
1587         } finally {
1588             vm.stop();
1589         }
1590     }
1591 
checkVmOutputIsRedirectedToLogcat(boolean debuggable)1592     private boolean checkVmOutputIsRedirectedToLogcat(boolean debuggable) throws Exception {
1593         String time =
1594                 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
1595         final VirtualMachineConfig vmConfig =
1596                 new VirtualMachineConfig.Builder(getContext())
1597                         .setProtectedVm(mProtectedVm)
1598                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1599                         .setDebugLevel(debuggable ? DEBUG_LEVEL_FULL : DEBUG_LEVEL_NONE)
1600                         .setVmOutputCaptured(false)
1601                         .build();
1602         final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_logcat", vmConfig);
1603 
1604         runVmTestService(TAG, vm, (service, results) -> {});
1605 
1606         // only check logs printed after this test
1607         Process logcatProcess =
1608                 new ProcessBuilder()
1609                         .command(
1610                                 "logcat",
1611                                 "-e",
1612                                 "virtualizationmanager::aidl: Console.*executing main task",
1613                                 "-t",
1614                                 time)
1615                         .start();
1616         logcatProcess.waitFor();
1617         BufferedReader reader =
1618                 new BufferedReader(new InputStreamReader(logcatProcess.getInputStream()));
1619         return !Strings.isNullOrEmpty(reader.readLine());
1620     }
1621 
1622     @Test
outputIsRedirectedToLogcatIfNotCaptured()1623     public void outputIsRedirectedToLogcatIfNotCaptured() throws Exception {
1624         assumeSupportedDevice();
1625 
1626         assertThat(checkVmOutputIsRedirectedToLogcat(true)).isTrue();
1627     }
1628 
setSystemProperties(String name, String value)1629     private boolean setSystemProperties(String name, String value) {
1630         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
1631         UiAutomation uiAutomation = instrumentation.getUiAutomation();
1632         String cmd = "setprop " + name + " " + (value.isEmpty() ? "\"\"" : value);
1633         return runInShellWithStderr(TAG, uiAutomation, cmd).trim().isEmpty();
1634     }
1635 
1636     @Test
outputIsNotRedirectedToLogcatIfNotDebuggable()1637     public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
1638         assumeSupportedDevice();
1639 
1640         // Disable debug policy to ensure no log output.
1641         String sysprop = "hypervisor.virtualizationmanager.debug_policy.path";
1642         String old = SystemProperties.get(sysprop);
1643         assumeTrue(
1644                 "Can't disable debug policy. Perhapse user build?",
1645                 setSystemProperties(sysprop, ""));
1646 
1647         try {
1648             assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
1649         } finally {
1650             assertThat(setSystemProperties(sysprop, old)).isTrue();
1651         }
1652     }
1653 
1654     @Test
testStartVmWithPayloadOfAnotherApp()1655     public void testStartVmWithPayloadOfAnotherApp() throws Exception {
1656         assumeSupportedDevice();
1657 
1658         Context ctx = getContext();
1659         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
1660 
1661         VirtualMachineConfig config =
1662                 new VirtualMachineConfig.Builder(otherAppCtx)
1663                         .setDebugLevel(DEBUG_LEVEL_FULL)
1664                         .setProtectedVm(isProtectedVm())
1665                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
1666                         .build();
1667 
1668         try (VirtualMachine vm = forceCreateNewVirtualMachine("vm_from_another_app", config)) {
1669             TestResults results =
1670                     runVmTestService(
1671                             TAG,
1672                             vm,
1673                             (ts, tr) -> {
1674                                 tr.mAddInteger = ts.addInteger(101, 303);
1675                             });
1676             assertThat(results.mAddInteger).isEqualTo(404);
1677         }
1678 
1679         getVirtualMachineManager().delete("vm_from_another_app");
1680     }
1681 
1682     @Test
testVmDescriptorParcelUnparcel_noTrustedStorage()1683     public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
1684         assumeSupportedDevice();
1685 
1686         VirtualMachineConfig config =
1687                 newVmConfigBuilder()
1688                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1689                         .setDebugLevel(DEBUG_LEVEL_FULL)
1690                         .build();
1691 
1692         VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
1693         // Just start & stop the VM.
1694         runVmTestService(TAG, originalVm, (ts, tr) -> {});
1695 
1696         // Now create the descriptor and manually parcel & unparcel it.
1697         VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
1698 
1699         if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
1700             getVirtualMachineManager().delete("import_vm_from_unparceled");
1701         }
1702 
1703         VirtualMachine importVm =
1704                 getVirtualMachineManager()
1705                         .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
1706 
1707         assertFileContentsAreEqualInTwoVms(
1708                 "config.xml", "original_vm", "import_vm_from_unparceled");
1709         assertFileContentsAreEqualInTwoVms(
1710                 "instance.img", "original_vm", "import_vm_from_unparceled");
1711 
1712         // Check that we can start and stop imported vm as well
1713         runVmTestService(TAG, importVm, (ts, tr) -> {});
1714     }
1715 
1716     @Test
testVmDescriptorParcelUnparcel_withTrustedStorage()1717     public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
1718         assumeSupportedDevice();
1719 
1720         VirtualMachineConfig config =
1721                 newVmConfigBuilder()
1722                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1723                         .setDebugLevel(DEBUG_LEVEL_FULL)
1724                         .setEncryptedStorageBytes(1_000_000)
1725                         .build();
1726 
1727         VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
1728         // Just start & stop the VM.
1729         {
1730             TestResults testResults =
1731                     runVmTestService(
1732                             TAG,
1733                             originalVm,
1734                             (ts, tr) -> {
1735                                 ts.writeToFile("not a secret!", "/mnt/encryptedstore/secret.txt");
1736                             });
1737             assertThat(testResults.mException).isNull();
1738         }
1739 
1740         // Now create the descriptor and manually parcel & unparcel it.
1741         VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
1742 
1743         if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
1744             getVirtualMachineManager().delete("import_vm_from_unparceled");
1745         }
1746 
1747         VirtualMachine importVm =
1748                 getVirtualMachineManager()
1749                         .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
1750 
1751         assertFileContentsAreEqualInTwoVms(
1752                 "config.xml", "original_vm", "import_vm_from_unparceled");
1753         assertFileContentsAreEqualInTwoVms(
1754                 "instance.img", "original_vm", "import_vm_from_unparceled");
1755         assertFileContentsAreEqualInTwoVms(
1756                 "storage.img", "original_vm", "import_vm_from_unparceled");
1757 
1758         TestResults testResults =
1759                 runVmTestService(
1760                         TAG,
1761                         importVm,
1762                         (ts, tr) -> {
1763                             tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/secret.txt");
1764                         });
1765 
1766         assertThat(testResults.mException).isNull();
1767         assertThat(testResults.mFileContent).isEqualTo("not a secret!");
1768     }
1769 
1770     @Test
testShareVmWithAnotherApp()1771     public void testShareVmWithAnotherApp() throws Exception {
1772         assumeSupportedDevice();
1773 
1774         Context ctx = getContext();
1775         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
1776 
1777         VirtualMachineConfig config =
1778                 new VirtualMachineConfig.Builder(otherAppCtx)
1779                         .setDebugLevel(DEBUG_LEVEL_FULL)
1780                         .setProtectedVm(isProtectedVm())
1781                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
1782                         .build();
1783 
1784         VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
1785         // Just start & stop the VM.
1786         runVmTestService(TAG, vm, (ts, tr) -> {});
1787         // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
1788         VirtualMachineDescriptor vmDesc = vm.toDescriptor();
1789 
1790         Intent serviceIntent = new Intent();
1791         serviceIntent.setComponent(
1792                 new ComponentName(
1793                         VM_SHARE_APP_PACKAGE_NAME,
1794                         "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
1795         serviceIntent.setAction("com.android.microdroid.test.sharevm.VmShareService");
1796 
1797         VmShareServiceConnection connection = new VmShareServiceConnection();
1798         boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
1799         assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
1800 
1801         IVmShareTestService service = connection.waitForService();
1802         assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
1803 
1804         try {
1805             // Send the VM descriptor to the other app. When received, it will reconstruct the VM
1806             // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
1807             // ITestService binder that delegates all the calls to the VM, and share it with this
1808             // app. It will allow us to verify assertions on the running VM in the other app.
1809             ITestService testServiceProxy = service.startVm(vmDesc);
1810 
1811             int result = testServiceProxy.addInteger(37, 73);
1812             assertThat(result).isEqualTo(110);
1813         } finally {
1814             ctx.unbindService(connection);
1815         }
1816     }
1817 
1818     @Test
testShareVmWithAnotherApp_encryptedStorage()1819     public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
1820         assumeSupportedDevice();
1821 
1822         Context ctx = getContext();
1823         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
1824 
1825         VirtualMachineConfig config =
1826                 new VirtualMachineConfig.Builder(otherAppCtx)
1827                         .setDebugLevel(DEBUG_LEVEL_FULL)
1828                         .setProtectedVm(isProtectedVm())
1829                         .setEncryptedStorageBytes(3_000_000)
1830                         .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
1831                         .build();
1832 
1833         VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
1834         // Just start & stop the VM.
1835         runVmTestService(
1836                 TAG,
1837                 vm,
1838                 (ts, tr) -> {
1839                     ts.writeToFile(EXAMPLE_STRING, "/mnt/encryptedstore/private.key");
1840                 });
1841         // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
1842         VirtualMachineDescriptor vmDesc = vm.toDescriptor();
1843 
1844         Intent serviceIntent = new Intent();
1845         serviceIntent.setComponent(
1846                 new ComponentName(
1847                         VM_SHARE_APP_PACKAGE_NAME,
1848                         "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
1849         serviceIntent.setAction("com.android.microdroid.test.sharevm.VmShareService");
1850 
1851         VmShareServiceConnection connection = new VmShareServiceConnection();
1852         boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
1853         assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
1854 
1855         IVmShareTestService service = connection.waitForService();
1856         assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
1857 
1858         try {
1859             // Send the VM descriptor to the other app. When received, it will reconstruct the VM
1860             // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
1861             // ITestService binder that delegates all the calls to the VM, and share it with this
1862             // app. It will allow us to verify assertions on the running VM in the other app.
1863             ITestService testServiceProxy = service.startVm(vmDesc);
1864 
1865             String result = testServiceProxy.readFromFile("/mnt/encryptedstore/private.key");
1866             assertThat(result).isEqualTo(EXAMPLE_STRING);
1867         } finally {
1868             ctx.unbindService(connection);
1869         }
1870     }
1871 
1872     @Test
1873     @CddTest(requirements = {"9.17/C-1-5"})
testFileUnderBinHasExecutePermission()1874     public void testFileUnderBinHasExecutePermission() throws Exception {
1875         assumeSupportedDevice();
1876 
1877         VirtualMachineConfig vmConfig =
1878                 newVmConfigBuilder()
1879                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1880                         .setMemoryBytes(minMemoryRequired())
1881                         .setDebugLevel(DEBUG_LEVEL_FULL)
1882                         .build();
1883         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_perms", vmConfig);
1884 
1885         TestResults testResults =
1886                 runVmTestService(
1887                         TAG,
1888                         vm,
1889                         (ts, tr) -> {
1890                             tr.mFileMode = ts.getFilePermissions("/mnt/apk/bin/measure_io");
1891                         });
1892 
1893         testResults.assertNoException();
1894         int allPermissionsMask =
1895                 OsConstants.S_IRUSR
1896                         | OsConstants.S_IWUSR
1897                         | OsConstants.S_IXUSR
1898                         | OsConstants.S_IRGRP
1899                         | OsConstants.S_IWGRP
1900                         | OsConstants.S_IXGRP
1901                         | OsConstants.S_IROTH
1902                         | OsConstants.S_IWOTH
1903                         | OsConstants.S_IXOTH;
1904         assertThat(testResults.mFileMode & allPermissionsMask)
1905                 .isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
1906     }
1907 
1908     // Taken from bionic/libs/kernel/uapi/linux/mounth.h.
1909     private static final int MS_NOEXEC = 8;
1910 
1911     @Test
1912     @CddTest(requirements = {"9.17/C-1-5"})
dataIsMountedWithNoExec()1913     public void dataIsMountedWithNoExec() throws Exception {
1914         assumeSupportedDevice();
1915 
1916         VirtualMachineConfig vmConfig =
1917                 newVmConfigBuilder()
1918                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1919                         .setDebugLevel(DEBUG_LEVEL_FULL)
1920                         .build();
1921         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_data_mount", vmConfig);
1922 
1923         TestResults testResults =
1924                 runVmTestService(
1925                         TAG,
1926                         vm,
1927                         (ts, tr) -> {
1928                             tr.mMountFlags = ts.getMountFlags("/data");
1929                         });
1930 
1931         assertThat(testResults.mException).isNull();
1932         assertWithMessage("/data should be mounted with MS_NOEXEC")
1933                 .that(testResults.mMountFlags & MS_NOEXEC)
1934                 .isEqualTo(MS_NOEXEC);
1935     }
1936 
1937     @Test
1938     @CddTest(requirements = {"9.17/C-1-5"})
encryptedStoreIsMountedWithNoExec()1939     public void encryptedStoreIsMountedWithNoExec() throws Exception {
1940         assumeSupportedDevice();
1941 
1942         VirtualMachineConfig vmConfig =
1943                 newVmConfigBuilder()
1944                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
1945                         .setDebugLevel(DEBUG_LEVEL_FULL)
1946                         .setEncryptedStorageBytes(4_000_000)
1947                         .build();
1948         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_encstore_no_exec", vmConfig);
1949 
1950         TestResults testResults =
1951                 runVmTestService(
1952                         TAG,
1953                         vm,
1954                         (ts, tr) -> {
1955                             tr.mMountFlags = ts.getMountFlags("/mnt/encryptedstore");
1956                         });
1957 
1958         assertThat(testResults.mException).isNull();
1959         assertWithMessage("/mnt/encryptedstore should be mounted with MS_NOEXEC")
1960                 .that(testResults.mMountFlags & MS_NOEXEC)
1961                 .isEqualTo(MS_NOEXEC);
1962     }
1963 
1964     @Test
1965     @VsrTest(requirements = {"VSR-7.1-001.003"})
kernelVersionRequirement()1966     public void kernelVersionRequirement() throws Exception {
1967         int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
1968         assume().withMessage("Skip on devices launched before Android 14 (API level 34)")
1969                 .that(firstApiLevel)
1970                 .isAtLeast(34);
1971 
1972         String[] tokens = KERNEL_VERSION.split("\\.");
1973         int major = Integer.parseInt(tokens[0]);
1974         int minor = Integer.parseInt(tokens[1]);
1975 
1976         // Check kernel version >= 5.15
1977         assertTrue(major >= 5);
1978         if (major == 5) {
1979             assertTrue(minor >= 15);
1980         }
1981     }
1982 
1983     private static class VmShareServiceConnection implements ServiceConnection {
1984 
1985         private final CountDownLatch mLatch = new CountDownLatch(1);
1986 
1987         private IVmShareTestService mVmShareTestService;
1988 
1989         @Override
onServiceConnected(ComponentName name, IBinder service)1990         public void onServiceConnected(ComponentName name, IBinder service) {
1991             mVmShareTestService = IVmShareTestService.Stub.asInterface(service);
1992             mLatch.countDown();
1993         }
1994 
1995         @Override
onServiceDisconnected(ComponentName name)1996         public void onServiceDisconnected(ComponentName name) {}
1997 
waitForService()1998         private IVmShareTestService waitForService() throws Exception {
1999             if (!mLatch.await(1, TimeUnit.MINUTES)) {
2000                 return null;
2001             }
2002             return mVmShareTestService;
2003         }
2004     }
2005 
toParcelFromParcel(VirtualMachineDescriptor descriptor)2006     private VirtualMachineDescriptor toParcelFromParcel(VirtualMachineDescriptor descriptor) {
2007         Parcel parcel = Parcel.obtain();
2008         descriptor.writeToParcel(parcel, 0);
2009         parcel.setDataPosition(0);
2010         return VirtualMachineDescriptor.CREATOR.createFromParcel(parcel);
2011     }
2012 
assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)2013     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
2014             throws IOException {
2015         File file1 = getVmFile(vmName1, fileName);
2016         File file2 = getVmFile(vmName2, fileName);
2017         try (FileInputStream input1 = new FileInputStream(file1);
2018                 FileInputStream input2 = new FileInputStream(file2)) {
2019             assertThat(Arrays.equals(input1.readAllBytes(), input2.readAllBytes())).isTrue();
2020         }
2021     }
2022 
getVmFile(String vmName, String fileName)2023     private File getVmFile(String vmName, String fileName) {
2024         Context context = getContext();
2025         Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName, fileName);
2026         return filePath.toFile();
2027     }
2028 
assertThrowsVmException(ThrowingRunnable runnable)2029     private void assertThrowsVmException(ThrowingRunnable runnable) {
2030         assertThrows(VirtualMachineException.class, runnable);
2031     }
2032 
assertThrowsVmExceptionContaining( ThrowingRunnable runnable, String expectedContents)2033     private void assertThrowsVmExceptionContaining(
2034             ThrowingRunnable runnable, String expectedContents) {
2035         Exception e = assertThrows(VirtualMachineException.class, runnable);
2036         assertThat(e).hasMessageThat().contains(expectedContents);
2037     }
2038 
minMemoryRequired()2039     private long minMemoryRequired() {
2040         if (Build.SUPPORTED_ABIS.length > 0) {
2041             String primaryAbi = Build.SUPPORTED_ABIS[0];
2042             switch (primaryAbi) {
2043                 case "x86_64":
2044                     return MIN_MEM_X86_64;
2045                 case "arm64-v8a":
2046                     return MIN_MEM_ARM64;
2047             }
2048         }
2049         return 0;
2050     }
2051 
assumeSupportedDevice()2052     private void assumeSupportedDevice() {
2053         assume()
2054                 .withMessage("Skip on 5.4 kernel. b/218303240")
2055                 .that(KERNEL_VERSION)
2056                 .isNotEqualTo("5.4");
2057     }
2058 }
2059