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