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