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