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 17 package com.android.microdroid.test; 18 19 import static com.android.microdroid.test.host.CommandResultSubject.command_results; 20 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder; 21 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 22 23 import static com.google.common.truth.Truth.assertThat; 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import static org.hamcrest.CoreMatchers.containsString; 27 import static org.junit.Assert.assertThat; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assume.assumeFalse; 31 import static org.junit.Assume.assumeTrue; 32 33 import static java.util.stream.Collectors.toList; 34 35 import android.cts.statsdatom.lib.ConfigUtils; 36 import android.cts.statsdatom.lib.ReportUtils; 37 38 import com.android.compatibility.common.util.CddTest; 39 import com.android.compatibility.common.util.GmsTest; 40 import com.android.compatibility.common.util.PropertyUtil; 41 import com.android.compatibility.common.util.VsrTest; 42 import com.android.microdroid.test.common.ProcessUtil; 43 import com.android.microdroid.test.host.CommandRunner; 44 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase; 45 import com.android.os.AtomsProto; 46 import com.android.os.StatsLog; 47 import com.android.tradefed.device.DeviceNotAvailableException; 48 import com.android.tradefed.device.DeviceRuntimeException; 49 import com.android.tradefed.device.ITestDevice; 50 import com.android.tradefed.device.TestDevice; 51 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics; 52 import com.android.tradefed.testtype.junit4.DeviceParameterizedRunner; 53 import com.android.tradefed.util.CommandResult; 54 import com.android.tradefed.util.CommandStatus; 55 import com.android.tradefed.util.FileUtil; 56 import com.android.tradefed.util.RunUtil; 57 import com.android.tradefed.util.xml.AbstractXmlParser; 58 import com.android.virt.PayloadMetadata; 59 60 import junitparams.Parameters; 61 import junitparams.naming.TestCaseName; 62 63 import org.json.JSONArray; 64 import org.json.JSONObject; 65 import org.junit.After; 66 import org.junit.Before; 67 import org.junit.Ignore; 68 import org.junit.Rule; 69 import org.junit.Test; 70 import org.junit.rules.TestName; 71 import org.junit.runner.RunWith; 72 import org.xml.sax.Attributes; 73 import org.xml.sax.helpers.DefaultHandler; 74 75 import java.io.ByteArrayInputStream; 76 import java.io.File; 77 import java.io.PipedInputStream; 78 import java.io.PipedOutputStream; 79 import java.util.ArrayList; 80 import java.util.Arrays; 81 import java.util.Collections; 82 import java.util.List; 83 import java.util.Map; 84 import java.util.Objects; 85 import java.util.concurrent.Callable; 86 import java.util.concurrent.TimeUnit; 87 import java.util.function.Function; 88 import java.util.regex.Matcher; 89 import java.util.regex.Pattern; 90 import java.util.stream.Collectors; 91 92 @RunWith(DeviceParameterizedRunner.class) 93 public class MicrodroidHostTests extends MicrodroidHostTestCaseBase { 94 private static final String APK_NAME = "MicrodroidTestApp.apk"; 95 private static final String APK_UPDATED_NAME = "MicrodroidTestAppUpdated.apk"; 96 private static final String PACKAGE_NAME = "com.android.microdroid.test"; 97 private static final String EMPTY_AOSP_PACKAGE_NAME = "com.android.microdroid.empty_payload"; 98 private static final String EMPTY_PACKAGE_NAME = "com.google.android.microdroid.empty_payload"; 99 private static final String SHELL_PACKAGE_NAME = "com.android.shell"; 100 private static final String VIRT_APEX = "/apex/com.android.virt/"; 101 private static final String INSTANCE_IMG = TEST_ROOT + "instance.img"; 102 private static final String INSTANCE_ID_FILE = TEST_ROOT + "instance_id"; 103 104 private static final String DEBUG_LEVEL_FULL = "full"; 105 private static final String DEBUG_LEVEL_NONE = "none"; 106 107 private static final int MIN_MEM_ARM64 = 170; 108 private static final int MIN_MEM_X86_64 = 196; 109 110 private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds 111 112 private static class VmInfo { 113 final Process mProcess; 114 VmInfo(Process process)115 VmInfo(Process process) { 116 mProcess = process; 117 } 118 } 119 params()120 public static List<Object[]> params() { 121 List<Object[]> ret = new ArrayList<>(); 122 for (Object[] osKey : osVersions()) { 123 ret.add(new Object[] {true /* protectedVm */, osKey[0]}); 124 ret.add(new Object[] {false /* protectedVm */, osKey[0]}); 125 } 126 return ret; 127 } 128 osVersions()129 public static List<Object[]> osVersions() { 130 return SUPPORTED_OSES.keySet().stream() 131 .map(osKey -> new Object[] {osKey}) 132 .collect(Collectors.toList()); 133 } 134 135 @Rule public TestLogData mTestLogs = new TestLogData(); 136 @Rule public TestName mTestName = new TestName(); 137 @Rule public TestMetrics mMetrics = new TestMetrics(); 138 139 private String mMetricPrefix; 140 141 private ITestDevice mMicrodroidDevice; 142 minMemorySize()143 private int minMemorySize() throws DeviceNotAvailableException { 144 CommandRunner android = new CommandRunner(getDevice()); 145 String abi = android.run("getprop", "ro.product.cpu.abi"); 146 assertThat(abi).isNotEmpty(); 147 if (abi.startsWith("arm64")) { 148 return MIN_MEM_ARM64; 149 } else if (abi.startsWith("x86_64")) { 150 return MIN_MEM_X86_64; 151 } 152 throw new AssertionError("Unsupported ABI: " + abi); 153 } 154 newPartition(String label, String path)155 private static JSONObject newPartition(String label, String path) { 156 return new JSONObject(Map.of("label", label, "path", path)); 157 } 158 createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata)159 private void createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata) 160 throws Exception { 161 PayloadMetadata.write( 162 PayloadMetadata.metadata( 163 "/mnt/apk/assets/vm_config.json", 164 PayloadMetadata.apk("microdroid-apk"), 165 apexes.stream() 166 .map(apex -> PayloadMetadata.apex(apex.name)) 167 .collect(toList())), 168 payloadMetadata); 169 } 170 resignVirtApex( File virtApexDir, File signingKey, Map<String, File> keyOverrides, boolean updateBootconfigs)171 private void resignVirtApex( 172 File virtApexDir, 173 File signingKey, 174 Map<String, File> keyOverrides, 175 boolean updateBootconfigs) { 176 File signVirtApex = findTestFile("sign_virt_apex"); 177 178 RunUtil runUtil = createRunUtil(); 179 // Set the parent dir on the PATH (e.g. <workdir>/bin) 180 String separator = System.getProperty("path.separator"); 181 String path = signVirtApex.getParentFile().getPath() + separator + System.getenv("PATH"); 182 runUtil.setEnvVariable("PATH", path); 183 184 List<String> command = new ArrayList<>(); 185 command.add(signVirtApex.getAbsolutePath()); 186 if (!updateBootconfigs) { 187 command.add("--do_not_update_bootconfigs"); 188 } 189 // In some cases we run a CTS binary that is built from a different branch that the /system 190 // image under test. In such cases we might end up in a situation when avb_version used in 191 // CTS binary and avb_version used to sign the com.android.virt APEX do not match. 192 // This is a weird configuration, but unfortunately it can happen, hence we pass here 193 // --do_not_validate_avb_version flag to make sure that CTS doesn't fail on it. 194 command.add("--do_not_validate_avb_version"); 195 keyOverrides.forEach( 196 (filename, keyFile) -> 197 command.add("--key_override " + filename + "=" + keyFile.getPath())); 198 command.add(signingKey.getPath()); 199 command.add(virtApexDir.getPath()); 200 201 CommandResult result = 202 runUtil.runTimedCmd( 203 // sign_virt_apex is so slow on CI server that this often times 204 // out. Until we can make it fast, use 50s for timeout 205 50 * 1000, "/bin/bash", "-c", String.join(" ", command)); 206 String out = result.getStdout(); 207 String err = result.getStderr(); 208 assertWithMessage( 209 "resigning the Virt APEX failed:\n\tout: " + out + "\n\terr: " + err + "\n") 210 .about(command_results()) 211 .that(result) 212 .isSuccess(); 213 } 214 assertThatEventually( long timeoutMillis, Callable<T> callable, org.hamcrest.Matcher<T> matcher)215 private static <T> void assertThatEventually( 216 long timeoutMillis, Callable<T> callable, org.hamcrest.Matcher<T> matcher) 217 throws Exception { 218 long start = System.currentTimeMillis(); 219 while ((System.currentTimeMillis() - start < timeoutMillis) 220 && !matcher.matches(callable.call())) { 221 RunUtil.getDefault().sleep(500); 222 } 223 assertThat(callable.call(), matcher); 224 } 225 getDeviceNumCpus(CommandRunner runner)226 private int getDeviceNumCpus(CommandRunner runner) throws DeviceNotAvailableException { 227 return Integer.parseInt(runner.run("nproc --all").trim()); 228 } 229 getDeviceNumCpus(ITestDevice device)230 private int getDeviceNumCpus(ITestDevice device) throws DeviceNotAvailableException { 231 return getDeviceNumCpus(new CommandRunner(device)); 232 } 233 234 static class ActiveApexInfo { 235 public String name; 236 public String path; 237 public boolean provideSharedApexLibs; 238 ActiveApexInfo(String name, String path, boolean provideSharedApexLibs)239 ActiveApexInfo(String name, String path, boolean provideSharedApexLibs) { 240 this.name = name; 241 this.path = path; 242 this.provideSharedApexLibs = provideSharedApexLibs; 243 } 244 } 245 246 static class ActiveApexInfoList { 247 private List<ActiveApexInfo> mList; 248 ActiveApexInfoList(List<ActiveApexInfo> list)249 ActiveApexInfoList(List<ActiveApexInfo> list) { 250 this.mList = list; 251 } 252 get(String apexName)253 ActiveApexInfo get(String apexName) { 254 return mList.stream() 255 .filter(info -> apexName.equals(info.name)) 256 .findFirst() 257 .orElse(null); 258 } 259 getSharedLibApexes()260 List<ActiveApexInfo> getSharedLibApexes() { 261 return mList.stream().filter(info -> info.provideSharedApexLibs).collect(toList()); 262 } 263 } 264 getActiveApexInfoList()265 private ActiveApexInfoList getActiveApexInfoList() throws Exception { 266 String apexInfoListXml = getDevice().pullFileContents("/apex/apex-info-list.xml"); 267 List<ActiveApexInfo> list = new ArrayList<>(); 268 new AbstractXmlParser() { 269 @Override 270 protected DefaultHandler createXmlHandler() { 271 return new DefaultHandler() { 272 @Override 273 public void startElement( 274 String uri, String localName, String qName, Attributes attributes) { 275 if (localName.equals("apex-info") 276 && attributes.getValue("isActive").equals("true")) { 277 String name = attributes.getValue("moduleName"); 278 String path = attributes.getValue("modulePath"); 279 String sharedApex = attributes.getValue("provideSharedApexLibs"); 280 list.add(new ActiveApexInfo(name, path, "true".equals(sharedApex))); 281 } 282 } 283 }; 284 } 285 }.parse(new ByteArrayInputStream(apexInfoListXml.getBytes())); 286 return new ActiveApexInfoList(list); 287 } 288 289 private VmInfo runMicrodroidWithResignedImages( 290 File key, 291 Map<String, File> keyOverrides, 292 boolean isProtected, 293 boolean updateBootconfigs, 294 String os) 295 throws Exception { 296 CommandRunner android = new CommandRunner(getDevice()); 297 os = SUPPORTED_OSES.get(os); 298 299 File virtApexDir = FileUtil.createTempDir("virt_apex"); 300 301 // Pull the virt apex's etc/ directory (which contains images and microdroid.json) 302 File virtApexEtcDir = new File(virtApexDir, "etc"); 303 // We need only etc/ directory for images 304 assertWithMessage("Failed to mkdir " + virtApexEtcDir) 305 .that(virtApexEtcDir.mkdirs()) 306 .isTrue(); 307 assertWithMessage("Failed to pull " + VIRT_APEX + "etc") 308 .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)) 309 .isTrue(); 310 311 resignVirtApex(virtApexDir, key, keyOverrides, updateBootconfigs); 312 313 // Push back re-signed virt APEX contents and updated microdroid.json 314 getDevice().pushDir(virtApexDir, TEST_ROOT); 315 316 // Create the idsig file for the APK 317 final String apkPath = getPathForPackage(PACKAGE_NAME); 318 final String idSigPath = TEST_ROOT + "idsig"; 319 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 320 321 // Create the instance image for the VM 322 final String instanceImgPath = TEST_ROOT + "instance.img"; 323 android.run( 324 VIRT_APEX + "bin/vm", 325 "create-partition", 326 "--type instance", 327 instanceImgPath, 328 Integer.toString(10 * 1024 * 1024)); 329 330 // payload-metadata is created on device 331 final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img"; 332 333 // Load /apex/apex-info-list.xml to get paths to APEXes required for the VM. 334 ActiveApexInfoList list = getActiveApexInfoList(); 335 336 // Since Java APP can't start a VM with a custom image, here, we start a VM using `vm run` 337 // command with a VM Raw config which is equiv. to what virtualizationservice creates with 338 // a VM App config. 339 // 340 // 1. use etc/microdroid.json as base 341 // 2. add partitions: bootconfig, vbmeta, instance image 342 // 3. add a payload image disk with 343 // - payload-metadata 344 // - apexes 345 // - test apk 346 // - its idsig 347 348 // Load etc/microdroid.json 349 File microdroidConfigFile = new File(virtApexEtcDir, os + ".json"); 350 JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile)); 351 352 // Replace paths so that the config uses re-signed images from TEST_ROOT 353 config.put("kernel", config.getString("kernel").replace(VIRT_APEX, TEST_ROOT)); 354 JSONArray disks = config.getJSONArray("disks"); 355 for (int diskIndex = 0; diskIndex < disks.length(); diskIndex++) { 356 JSONObject disk = disks.getJSONObject(diskIndex); 357 JSONArray partitions = disk.getJSONArray("partitions"); 358 for (int partIndex = 0; partIndex < partitions.length(); partIndex++) { 359 JSONObject part = partitions.getJSONObject(partIndex); 360 part.put("path", part.getString("path").replace(VIRT_APEX, TEST_ROOT)); 361 } 362 } 363 364 // Add partitions to the second disk 365 final String initrdPath = TEST_ROOT + "etc/" + os + "_initrd_debuggable.img"; 366 config.put("initrd", initrdPath); 367 // Add instance image as a partition in disks[1] 368 disks.put( 369 new JSONObject() 370 .put("writable", true) 371 .put( 372 "partitions", 373 new JSONArray().put(newPartition("vm-instance", instanceImgPath)))); 374 // Add payload image disk with partitions: 375 // - payload-metadata 376 // - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional) 377 // - apk and idsig 378 List<ActiveApexInfo> apexesForVm = new ArrayList<>(); 379 apexesForVm.add(list.get("com.android.os.statsd")); 380 apexesForVm.add(list.get("com.android.adbd")); 381 apexesForVm.addAll(list.getSharedLibApexes()); 382 383 final JSONArray partitions = new JSONArray(); 384 partitions.put(newPartition("payload-metadata", payloadMetadataPath)); 385 for (ActiveApexInfo apex : apexesForVm) { 386 partitions.put(newPartition(apex.name, apex.path)); 387 } 388 partitions 389 .put(newPartition("microdroid-apk", apkPath)) 390 .put(newPartition("microdroid-apk-idsig", idSigPath)); 391 disks.put(new JSONObject().put("writable", false).put("partitions", partitions)); 392 393 final File localPayloadMetadata = new File(virtApexDir, "payload-metadata.img"); 394 createPayloadMetadata(apexesForVm, localPayloadMetadata); 395 getDevice().pushFile(localPayloadMetadata, payloadMetadataPath); 396 397 config.put("protected", isProtected); 398 399 // Write updated raw config 400 final String configPath = TEST_ROOT + "raw_config.json"; 401 getDevice().pushString(config.toString(), configPath); 402 403 List<String> args = 404 Arrays.asList( 405 "adb", 406 "-s", 407 getDevice().getSerialNumber(), 408 "shell", 409 VIRT_APEX + "bin/vm run", 410 "--console " + CONSOLE_PATH, 411 "--log " + LOG_PATH, 412 "--name " + "microdroid", // to still be seen as microdroid vm 413 configPath); 414 415 PipedInputStream pis = new PipedInputStream(); 416 Process process = createRunUtil().runCmdInBackground(args, new PipedOutputStream(pis)); 417 return new VmInfo(process); 418 } 419 420 @Test 421 @CddTest 422 @GmsTest(requirements = {"GMS-3-7.1-002", "GMS-VSR-7.1-001.006"}) 423 @VsrTest(requirements = {"VSR-7.1-001.007"}) 424 public void UpgradedPackageIsAcceptedWithSecretkeeper() throws Exception { 425 // Preconditions 426 assumeVmTypeSupported("microdroid", true); // Non-protected VMs may not support upgrades 427 ensureUpdatableVmSupported(); 428 getDevice().uninstallPackage(PACKAGE_NAME); 429 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ true); 430 ensureProtectedMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 431 432 getDevice().uninstallPackage(PACKAGE_NAME); 433 cleanUpVirtualizationTestSetup(getDevice()); 434 // Install the updated version of app (versionCode 6) 435 getDevice().installPackage(findTestFile(APK_UPDATED_NAME), /* reinstall= */ true); 436 ensureProtectedMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 437 } 438 439 @Test 440 @CddTest 441 @GmsTest(requirements = {"GMS-3-7.1-002", "GMS-VSR-7.1-001.006"}) 442 @VsrTest(requirements = {"VSR-7.1-001.007"}) 443 public void DowngradedPackageIsRejectedProtectedVm() throws Exception { 444 // Preconditions: Rollback protection is provided only for protected VM. 445 assumeVmTypeSupported("microdroid", true); 446 447 // Install the upgraded version (v6) 448 getDevice().uninstallPackage(PACKAGE_NAME); 449 getDevice().installPackage(findTestFile(APK_UPDATED_NAME), /* reinstall= */ true); 450 ensureProtectedMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 451 452 getDevice().uninstallPackage(PACKAGE_NAME); 453 cleanUpVirtualizationTestSetup(getDevice()); 454 // Install the older version (v5) 455 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ true); 456 457 assertThrows( 458 "pVM must fail to boot with downgraded payload apk", 459 DeviceRuntimeException.class, 460 () -> ensureProtectedMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG)); 461 } 462 463 private void ensureProtectedMicrodroidBootsSuccessfully( 464 String instanceIdPath, String instanceImgPath) throws DeviceNotAvailableException { 465 final String configPath = "assets/vm_config.json"; 466 ITestDevice microdroid = null; 467 int timeout = 30000; // 30 seconds 468 try { 469 microdroid = 470 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 471 .debugLevel(DEBUG_LEVEL_FULL) 472 .memoryMib(minMemorySize()) 473 .cpuTopology("match_host") 474 .protectedVm(true) 475 .instanceIdFile(instanceIdPath) 476 .instanceImgFile(instanceImgPath) 477 .setAdbConnectTimeoutMs(timeout) 478 .build(getAndroidDevice()); 479 assertThat(microdroid.waitForBootComplete(timeout)).isTrue(); 480 assertThat(microdroid.enableAdbRoot()).isTrue(); 481 } finally { 482 if (microdroid != null) { 483 getAndroidDevice().shutdownMicrodroid(microdroid); 484 } 485 } 486 } 487 488 @Test 489 @Parameters(method = "osVersions") 490 @TestCaseName("{method}_os_{0}") 491 @GmsTest(requirements = {"GMS-3-7.1-010"}) 492 public void protectedVmRunsPvmfw(String os) throws Exception { 493 // Arrange 494 assumeKernelSupported(os); 495 assumeVmTypeSupported(os, true); 496 final String configPath = "assets/vm_config_apex.json"; 497 498 // Act 499 MicrodroidBuilder microdroidBuilder = 500 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 501 .debugLevel(DEBUG_LEVEL_FULL) 502 .memoryMib(minMemorySize()) 503 .cpuTopology("match_host") 504 .protectedVm(true) 505 .name("protected_vm_runs_pvmfw"); 506 507 // --os flag was introduced in SDK 36 508 if (getAndroidDevice().getApiLevel() >= 36) { 509 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 510 } 511 512 mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice()); 513 514 // Assert 515 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 516 String consoleLog = getDevice().pullFileContents(TRADEFED_CONSOLE_PATH); 517 assertWithMessage("Failed to verify that pvmfw started") 518 .that(consoleLog) 519 .contains("pVM firmware"); 520 assertWithMessage("pvmfw failed to start kernel") 521 .that(consoleLog) 522 .contains("Starting payload..."); 523 // TODO(b/260994818): Investigate the feasibility of checking DeathReason. 524 } 525 526 @Test 527 @Parameters(method = "osVersions") 528 @TestCaseName("{method}_os_{0}") 529 @GmsTest(requirements = {"GMS-3-7.1-003", "GMS-3-7.1-010"}) 530 public void protectedVmWithImageSignedWithDifferentKeyFailsToVerifyPayload(String os) 531 throws Exception { 532 assumeKernelSupported(os); 533 assumeVmTypeSupported(os, true); 534 File key = findTestFile("test.com.android.virt.pem"); 535 536 // Act 537 VmInfo vmInfo = 538 runMicrodroidWithResignedImages( 539 key, 540 /* keyOverrides= */ Map.of(), 541 /* isProtected= */ true, 542 /* updateBootconfigs= */ true, 543 os); 544 545 // Assert 546 vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS); 547 String consoleLog = getDevice().pullFileContents(CONSOLE_PATH); 548 assertWithMessage("pvmfw should start").that(consoleLog).contains("pVM firmware"); 549 assertWithMessage("pvmfw should fail to verify the payload") 550 .that(consoleLog) 551 .contains("Failed to verify the payload"); 552 vmInfo.mProcess.destroy(); 553 } 554 555 @Test 556 @Parameters(method = "osVersions") 557 @TestCaseName("{method}_os_{0}") 558 @GmsTest(requirements = {"GMS-3-7.1-003", "GMS-3-7.1-010"}) 559 public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey(String os) 560 throws Exception { 561 // Preconditions 562 assumeKernelSupported(os); 563 assumeVmTypeSupported(os, false); 564 565 File key = findTestFile("test.com.android.virt.pem"); 566 Map<String, File> keyOverrides = Map.of(); 567 VmInfo vmInfo = 568 runMicrodroidWithResignedImages( 569 key, 570 keyOverrides, 571 /* isProtected= */ false, 572 /* updateBootconfigs= */ true, 573 os); 574 assertThatEventually( 575 100000, 576 () -> 577 getDevice().pullFileContents(CONSOLE_PATH) 578 + getDevice().pullFileContents(LOG_PATH), 579 containsString("boot completed, time to run payload")); 580 581 vmInfo.mProcess.destroy(); 582 } 583 584 @Test 585 @Parameters(method = "osVersions") 586 @TestCaseName("{method}_os_{0}") 587 @GmsTest(requirements = {"GMS-3-7.1-006"}) 588 public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig(String os) throws Exception { 589 // protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case. 590 assumeKernelSupported(os); 591 assumeVmTypeSupported(os, false); 592 593 // Sign everything with key1 except vbmeta 594 File key = findTestFile("test.com.android.virt.pem"); 595 // To be able to stop it, it should be a daemon. 596 VmInfo vmInfo = 597 runMicrodroidWithResignedImages( 598 key, 599 Map.of(), 600 /* isProtected= */ false, 601 /* updateBootconfigs= */ false, 602 os); 603 // Wait so that init can print errors to console (time in cuttlefish >> in real device) 604 assertThatEventually( 605 100000, 606 () -> getDevice().pullFileContents(CONSOLE_PATH), 607 containsString("init: [libfs_avb] Failed to verify vbmeta digest")); 608 vmInfo.mProcess.destroy(); 609 } 610 611 private void waitForCrosvmExit(CommandRunner android, String testStartTime) throws Exception { 612 // TODO: improve crosvm exit check. b/258848245 613 android.runWithTimeout( 614 15000, 615 "logcat", 616 "-m", 617 "1", 618 "-e", 619 "'virtualizationmanager::crosvm.*exited with status exit status:'", 620 "-T", 621 "'" + testStartTime + "'"); 622 } 623 624 private boolean isTombstoneReceivedFromHostLogcat(String testStartTime) throws Exception { 625 // Note this method relies on logcat values being printed by the receiver on host 626 // userspace crash log: virtualizationservice/src/aidl.rs 627 // kernel ramdump log: virtualizationmanager/src/crosvm.rs 628 String ramdumpRegex = 629 "Received [0-9]+ bytes from guest & wrote to tombstone file|" 630 + "Ramdump \"[^ ]+/ramdump\" sent to tombstoned"; 631 632 String result = 633 tryRunOnHost( 634 "timeout", 635 "3s", 636 "adb", 637 "-s", 638 getDevice().getSerialNumber(), 639 "logcat", 640 "-m", 641 "1", 642 "-e", 643 ramdumpRegex, 644 "-T", 645 testStartTime); 646 return !result.trim().isEmpty(); 647 } 648 649 private boolean isTombstoneGeneratedWithCmd( 650 boolean protectedVm, String os, String configPath, String... crashCommand) 651 throws Exception { 652 CommandRunner android = new CommandRunner(getDevice()); 653 String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'"); 654 655 MicrodroidBuilder microdroidBuilder = 656 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 657 .debugLevel(DEBUG_LEVEL_FULL) 658 .memoryMib(minMemorySize()) 659 .cpuTopology("match_host") 660 .protectedVm(protectedVm); 661 662 if (getAndroidDevice().getApiLevel() >= 36) { 663 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 664 } 665 666 mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice()); 667 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 668 mMicrodroidDevice.enableAdbRoot(); 669 670 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 671 // can crash in the middle of crashCommand; fail is ok 672 microdroid.tryRun(crashCommand); 673 674 // check until microdroid is shut down 675 waitForCrosvmExit(android, testStartTime); 676 677 return isTombstoneReceivedFromHostLogcat(testStartTime); 678 } 679 680 @Test 681 @Parameters(method = "params") 682 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 683 public void testTombstonesAreGeneratedUponUserspaceCrash(boolean protectedVm, String os) 684 throws Exception { 685 // Preconditions 686 assumeKernelSupported(os); 687 assumeVmTypeSupported(os, protectedVm); 688 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 689 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 690 assertThat( 691 isTombstoneGeneratedWithCmd( 692 protectedVm, 693 os, 694 "assets/vm_config.json", 695 "kill", 696 "-SIGSEGV", 697 "$(pidof microdroid_launcher)")) 698 .isTrue(); 699 } 700 701 @Test 702 @Parameters(method = "params") 703 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 704 public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash( 705 boolean protectedVm, String os) throws Exception { 706 // Preconditions 707 assumeKernelSupported(os); 708 assumeVmTypeSupported(os, protectedVm); 709 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 710 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 711 assertThat( 712 isTombstoneGeneratedWithCmd( 713 protectedVm, 714 os, 715 "assets/vm_config_no_tombstone.json", 716 "kill", 717 "-SIGSEGV", 718 "$(pidof microdroid_launcher)")) 719 .isFalse(); 720 } 721 722 @Test 723 @Parameters(method = "params") 724 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 725 @Ignore("b/341087884") // TODO(b/341087884): fix & re-enable 726 public void testTombstonesAreGeneratedUponKernelCrash(boolean protectedVm, String os) 727 throws Exception { 728 // Preconditions 729 assumeKernelSupported(os); 730 assumeVmTypeSupported(os, protectedVm); 731 assumeFalse("Cuttlefish is not supported", isCuttlefish()); 732 assumeFalse("Skipping test because ramdump is disabled on user build", isUserBuild()); 733 734 // Act 735 assertThat( 736 isTombstoneGeneratedWithCmd( 737 protectedVm, 738 os, 739 "assets/vm_config.json", 740 "echo", 741 "c", 742 ">", 743 "/proc/sysrq-trigger")) 744 .isTrue(); 745 } 746 747 private boolean isTombstoneGeneratedWithVmRunApp( 748 boolean protectedVm, String os, boolean debuggable, String... additionalArgs) 749 throws Exception { 750 // we can't use microdroid builder as it wants ADB connection (debuggable) 751 CommandRunner android = new CommandRunner(getDevice()); 752 String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'"); 753 os = SUPPORTED_OSES.get(os); 754 755 android.run("rm", "-rf", TEST_ROOT + "*"); 756 android.run("mkdir", "-p", TEST_ROOT + "*"); 757 758 final String apkPath = getPathForPackage(PACKAGE_NAME); 759 final String idsigPath = TEST_ROOT + "idsig"; 760 final String instanceImgPath = TEST_ROOT + "instance.img"; 761 final String instanceIdPath = TEST_ROOT + "instance_id"; 762 List<String> cmd = 763 new ArrayList<>( 764 Arrays.asList( 765 VIRT_APEX + "bin/vm", 766 "run-app", 767 "--debug", 768 debuggable ? DEBUG_LEVEL_FULL : DEBUG_LEVEL_NONE, 769 apkPath, 770 idsigPath, 771 instanceImgPath)); 772 if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) { 773 cmd.add("--instance-id-file"); 774 cmd.add(instanceIdPath); 775 } 776 ; 777 if (protectedVm) { 778 cmd.add("--protected"); 779 } 780 cmd.add("--os"); 781 cmd.add(os); 782 Collections.addAll(cmd, additionalArgs); 783 784 android.run(cmd.toArray(new String[0])); 785 return isTombstoneReceivedFromHostLogcat(testStartTime); 786 } 787 788 private boolean isTombstoneGeneratedWithCrashPayload( 789 boolean protectedVm, String os, boolean debuggable) throws Exception { 790 return isTombstoneGeneratedWithVmRunApp( 791 protectedVm, 792 os, 793 debuggable, 794 "--payload-binary-name", 795 "MicrodroidCrashNativeLib.so"); 796 } 797 798 @Test 799 @Parameters(method = "params") 800 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 801 public void testTombstonesAreGeneratedWithCrashPayload(boolean protectedVm, String os) 802 throws Exception { 803 // Preconditions 804 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 805 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 806 assumeKernelSupported(os); 807 assumeVmTypeSupported(os, protectedVm); 808 809 // Act 810 assertThat(isTombstoneGeneratedWithCrashPayload(protectedVm, os, /* debuggable= */ true)) 811 .isTrue(); 812 } 813 814 @Test 815 @Parameters(method = "params") 816 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 817 public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggable( 818 boolean protectedVm, String os) throws Exception { 819 // Preconditions 820 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 821 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 822 assumeKernelSupported(os); 823 assumeVmTypeSupported(os, protectedVm); 824 825 // Act 826 assertThat(isTombstoneGeneratedWithCrashPayload(protectedVm, os, /* debuggable= */ false)) 827 .isFalse(); 828 } 829 830 private boolean isTombstoneGeneratedWithCrashConfig( 831 boolean protectedVm, String os, boolean debuggable) throws Exception { 832 return isTombstoneGeneratedWithVmRunApp( 833 protectedVm, os, debuggable, "--config-path", "assets/vm_config_crash.json"); 834 } 835 836 @Test 837 @Parameters(method = "params") 838 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 839 public void testTombstonesAreGeneratedWithCrashConfig(boolean protectedVm, String os) 840 throws Exception { 841 // Preconditions 842 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 843 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 844 assumeKernelSupported(os); 845 assumeVmTypeSupported(os, protectedVm); 846 847 // Act 848 assertThat(isTombstoneGeneratedWithCrashConfig(protectedVm, os, /* debuggable= */ true)) 849 .isTrue(); 850 } 851 852 @Test 853 @Parameters(method = "params") 854 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 855 public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggable( 856 boolean protectedVm, String os) throws Exception { 857 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 858 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 859 assumeKernelSupported(os); 860 assumeVmTypeSupported(os, protectedVm); 861 assertThat(isTombstoneGeneratedWithCrashConfig(protectedVm, os, /* debuggable= */ false)) 862 .isFalse(); 863 } 864 865 @Test 866 @Parameters(method = "params") 867 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 868 public void testTelemetryPushedAtoms(boolean protectedVm, String os) throws Exception { 869 assumeKernelSupported(os); 870 assumeVmTypeSupported(os, protectedVm); 871 // Reset statsd config and report before the test 872 ConfigUtils.removeConfig(getDevice()); 873 ReportUtils.clearReports(getDevice()); 874 875 // Setup statsd config 876 int[] atomIds = { 877 AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER, 878 AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER, 879 AtomsProto.Atom.VM_EXITED_FIELD_NUMBER, 880 }; 881 ConfigUtils.uploadConfigForPushedAtoms(getDevice(), PACKAGE_NAME, atomIds); 882 883 // Create VM with microdroid 884 TestDevice device = getAndroidDevice(); 885 final String configPath = "assets/vm_config_apex.json"; // path inside the APK 886 MicrodroidBuilder microdroidBuilder = 887 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 888 .debugLevel(DEBUG_LEVEL_FULL) 889 .memoryMib(minMemorySize()) 890 .cpuTopology("match_host") 891 .protectedVm(protectedVm) 892 .name("test_telemetry_pushed_atoms"); 893 894 if (device.getApiLevel() >= 36) { 895 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 896 } 897 898 ITestDevice microdroid = microdroidBuilder.build(device); 899 900 microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 901 device.shutdownMicrodroid(microdroid); 902 903 // Try to collect atoms for 60000 milliseconds. 904 List<StatsLog.EventMetricData> data = new ArrayList<>(); 905 long start = System.currentTimeMillis(); 906 while ((System.currentTimeMillis() - start < 60000) && data.size() < 3) { 907 data.addAll(ReportUtils.getEventMetricDataList(getDevice())); 908 Thread.sleep(500); 909 } 910 assertThat( 911 data.stream() 912 .map(x -> x.getAtom().getPushedCase().getNumber()) 913 .collect(Collectors.toList())) 914 .containsExactly( 915 AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER, 916 AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER, 917 AtomsProto.Atom.VM_EXITED_FIELD_NUMBER) 918 .inOrder(); 919 920 // Check VmCreationRequested atom 921 AtomsProto.VmCreationRequested atomVmCreationRequested = 922 data.get(0).getAtom().getVmCreationRequested(); 923 if (isPkvmHypervisor()) { 924 assertThat(atomVmCreationRequested.getHypervisor()) 925 .isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM); 926 } 927 assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(protectedVm); 928 assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue(); 929 assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0); 930 assertThat(atomVmCreationRequested.getVmIdentifier()) 931 .isEqualTo("test_telemetry_pushed_atoms"); 932 assertThat(atomVmCreationRequested.getConfigType()) 933 .isEqualTo(AtomsProto.VmCreationRequested.ConfigType.VIRTUAL_MACHINE_APP_CONFIG); 934 assertThat(atomVmCreationRequested.getNumCpus()).isEqualTo(getDeviceNumCpus(device)); 935 assertThat(atomVmCreationRequested.getMemoryMib()).isEqualTo(minMemorySize()); 936 assertThat(atomVmCreationRequested.getApexes()) 937 .isEqualTo("com.android.art:com.android.compos:com.android.sdkext"); 938 939 // Check VmBooted atom 940 AtomsProto.VmBooted atomVmBooted = data.get(1).getAtom().getVmBooted(); 941 assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("test_telemetry_pushed_atoms"); 942 943 // Check VmExited atom 944 AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited(); 945 assertThat(atomVmExited.getVmIdentifier()).isEqualTo("test_telemetry_pushed_atoms"); 946 assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED); 947 assertThat(atomVmExited.getExitSignal()).isEqualTo(9); 948 // In CPU & memory related fields, check whether positive values are collected or not. 949 if (isPkvmHypervisor()) { 950 // Guest Time may not be updated on other hypervisors. 951 // Checking only if the hypervisor is PKVM. 952 assertThat(atomVmExited.getGuestTimeMillis()).isGreaterThan(0); 953 } 954 assertThat(atomVmExited.getRssVmKb()).isGreaterThan(0); 955 assertThat(atomVmExited.getRssCrosvmKb()).isGreaterThan(0); 956 957 // Check UID and elapsed_time by comparing each other. 958 assertThat(atomVmBooted.getUid()).isEqualTo(atomVmCreationRequested.getUid()); 959 assertThat(atomVmExited.getUid()).isEqualTo(atomVmCreationRequested.getUid()); 960 assertThat(atomVmBooted.getElapsedTimeMillis()) 961 .isLessThan(atomVmExited.getElapsedTimeMillis()); 962 } 963 964 private void testMicrodroidBootsWithBuilder(MicrodroidBuilder builder) throws Exception { 965 CommandRunner android = new CommandRunner(getDevice()); 966 967 mMicrodroidDevice = builder.build(getAndroidDevice()); 968 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 969 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 970 971 String vmList = android.run("/apex/com.android.virt/bin/vm list"); 972 assertThat(vmList).contains("requesterUid: " + android.run("id -u")); 973 974 // Test writing to /data partition 975 microdroid.run("echo MicrodroidTest > /data/local/tmp/test.txt"); 976 assertThat(microdroid.run("cat /data/local/tmp/test.txt")).isEqualTo("MicrodroidTest"); 977 978 // Check if the APK & its idsig partitions exist 979 final String apkPartition = "/dev/block/by-name/microdroid-apk"; 980 assertThat(microdroid.run("ls", apkPartition)).isEqualTo(apkPartition); 981 final String apkIdsigPartition = "/dev/block/by-name/microdroid-apk-idsig"; 982 assertThat(microdroid.run("ls", apkIdsigPartition)).isEqualTo(apkIdsigPartition); 983 // Check the vm-instance partition as well 984 final String vmInstancePartition = "/dev/block/by-name/vm-instance"; 985 assertThat(microdroid.run("ls", vmInstancePartition)).isEqualTo(vmInstancePartition); 986 987 // Check if the native library in the APK is has correct filesystem info 988 final String[] abis = microdroid.run("getprop", "ro.product.cpu.abilist").split(","); 989 assertWithMessage("Incorrect ABI list").that(abis).hasLength(1); 990 991 // Check that no denials have happened so far 992 String consoleText = getDevice().pullFileContents(TRADEFED_CONSOLE_PATH); 993 assertWithMessage("Console output shouldn't be empty").that(consoleText).isNotEmpty(); 994 String logText = getDevice().pullFileContents(TRADEFED_LOG_PATH); 995 assertWithMessage("Log output shouldn't be empty").that(logText).isNotEmpty(); 996 997 assertWithMessage("Unexpected denials during VM boot") 998 .that(consoleText + logText) 999 .doesNotContainMatch("avc:\\s+denied"); 1000 1001 assertThat(getDeviceNumCpus(microdroid)).isEqualTo(getDeviceNumCpus(android)); 1002 1003 // Check that selinux is enabled 1004 assertWithMessage("SELinux should be in enforcing mode") 1005 .that(microdroid.run("getenforce")) 1006 .isEqualTo("Enforcing"); 1007 1008 // TODO(b/176805428): adb is broken for nested VM 1009 if (!isCuttlefish()) { 1010 // Check neverallow rules on microdroid 1011 File policyFile = mMicrodroidDevice.pullFile("/sys/fs/selinux/policy"); 1012 File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf"); 1013 File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze"); 1014 1015 CommandResult result = 1016 createRunUtil() 1017 .runTimedCmd( 1018 10000, 1019 sepolicyAnalyzeBin.getPath(), 1020 policyFile.getPath(), 1021 "neverallow", 1022 "-w", 1023 "-f", 1024 generalPolicyConfFile.getPath()); 1025 assertWithMessage("neverallow check failed: " + result.getStderr().trim()) 1026 .about(command_results()) 1027 .that(result) 1028 .isSuccess(); 1029 } 1030 } 1031 1032 @Test 1033 @Parameters(method = "params") 1034 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 1035 @CddTest 1036 @GmsTest(requirements = {"GMS-3-7.1-001.002"}) 1037 public void testMicrodroidBoots(boolean protectedVm, String os) throws Exception { 1038 // Preconditions 1039 assumeKernelSupported(os); 1040 assumeVmTypeSupported(os, protectedVm); 1041 1042 final String configPath = "assets/vm_config.json"; // path inside the APK 1043 MicrodroidBuilder microdroidBuilder = 1044 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1045 .debugLevel(DEBUG_LEVEL_FULL) 1046 .memoryMib(minMemorySize()) 1047 .cpuTopology("match_host") 1048 .protectedVm(protectedVm) 1049 .name("test_microdroid_boots"); 1050 if (getAndroidDevice().getApiLevel() >= 36) { 1051 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 1052 } 1053 1054 testMicrodroidBootsWithBuilder(microdroidBuilder); 1055 } 1056 1057 @Test 1058 public void testMicrodroidRamUsage_protectedVm_true_os_microdroid() throws Exception { 1059 checkMicrodroidRamUsage(/* protectedVm= */ true, /* os= */ "microdroid"); 1060 } 1061 1062 @Test 1063 public void testMicrodroidRamUsage_protectedVm_false_os_microdroid() throws Exception { 1064 checkMicrodroidRamUsage(/* protectedVm= */ false, /* os= */ "microdroid"); 1065 } 1066 1067 @Test 1068 public void testMicrodroidRamUsage_protectedVm_true_os_android15_66() throws Exception { 1069 checkMicrodroidRamUsage(/* protectedVm= */ true, /* os= */ "android15_66"); 1070 } 1071 1072 @Test 1073 public void testMicrodroidRamUsage_protectedVm_false_os_android15_66() throws Exception { 1074 checkMicrodroidRamUsage(/* protectedVm= */ false, /* os= */ "android15_66"); 1075 } 1076 1077 // TODO(b/209036125): Upgrade this function to a parameterized test once metrics can be 1078 // collected with tradefed parameterizer. 1079 void checkMicrodroidRamUsage(boolean protectedVm, String os) throws Exception { 1080 // Preconditions 1081 assumeKernelSupported(os); 1082 assumeVmTypeSupported(os, protectedVm); 1083 1084 final String configPath = "assets/vm_config.json"; 1085 MicrodroidBuilder microdroidBuilder = 1086 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1087 .debugLevel(DEBUG_LEVEL_FULL) 1088 .memoryMib(minMemorySize()) 1089 .cpuTopology("match_host") 1090 .protectedVm(protectedVm) 1091 .name("test_microdroid_ram_usage"); 1092 if (getAndroidDevice().getApiLevel() >= 36) { 1093 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 1094 } 1095 1096 mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice()); 1097 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 1098 mMicrodroidDevice.enableAdbRoot(); 1099 1100 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 1101 Function<String, String> microdroidExec = 1102 (cmd) -> { 1103 try { 1104 return microdroid.run(cmd); 1105 } catch (Exception ex) { 1106 throw new IllegalStateException(ex); 1107 } 1108 }; 1109 1110 for (Map.Entry<String, Long> stat : 1111 ProcessUtil.getProcessMemoryMap(microdroidExec).entrySet()) { 1112 mMetrics.addTestMetric( 1113 mMetricPrefix + "meminfo/" + stat.getKey().toLowerCase(), 1114 stat.getValue().toString()); 1115 } 1116 1117 for (Map.Entry<Integer, String> proc : 1118 ProcessUtil.getProcessMap(microdroidExec).entrySet()) { 1119 for (Map.Entry<String, Long> stat : 1120 ProcessUtil.getProcessSmapsRollup(proc.getKey(), microdroidExec).entrySet()) { 1121 String name = stat.getKey().toLowerCase(); 1122 mMetrics.addTestMetric( 1123 mMetricPrefix + "smaps/" + name + "/" + proc.getValue(), 1124 stat.getValue().toString()); 1125 } 1126 } 1127 } 1128 1129 @Test 1130 @CddTest 1131 public void testPathToBinaryIsRejected() throws Exception { 1132 CommandRunner android = new CommandRunner(getDevice()); 1133 1134 // Create the idsig file for the APK 1135 final String apkPath = getPathForPackage(PACKAGE_NAME); 1136 final String idSigPath = TEST_ROOT + "idsig"; 1137 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 1138 // Create the instance image for the VM 1139 final String instanceImgPath = TEST_ROOT + "instance.img"; 1140 android.run( 1141 VIRT_APEX + "bin/vm", 1142 "create-partition", 1143 "--type instance", 1144 instanceImgPath, 1145 Integer.toString(10 * 1024 * 1024)); 1146 1147 List<String> cmd = 1148 new ArrayList<>( 1149 Arrays.asList( 1150 VIRT_APEX + "bin/vm", 1151 "run-app", 1152 "--payload-binary-name", 1153 "./MicrodroidTestNativeLib.so", 1154 apkPath, 1155 idSigPath, 1156 instanceImgPath)); 1157 if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) { 1158 cmd.add("--instance-id-file"); 1159 cmd.add(TEST_ROOT + "instance_id"); 1160 } 1161 1162 final String ret = android.runForResult(String.join(" ", cmd)).getStderr().trim(); 1163 1164 assertThat(ret).contains("Payload binary name must not specify a path"); 1165 } 1166 1167 private boolean hasAppPackage(String pkgName, CommandRunner android) throws DeviceNotAvailableException { 1168 String hasPackage = 1169 android.run( 1170 "pm list package | grep -w " + pkgName + " 1> /dev/null" + "; echo $?"); 1171 if (hasPackage.equals("0")) { 1172 return true; 1173 } 1174 1175 return false; 1176 } 1177 1178 @Test 1179 @CddTest 1180 public void testRunEmptyPayload() throws Exception { 1181 assumeVmTypeSupported("microdroid", false); 1182 1183 CommandRunner android = new CommandRunner(getDevice()); 1184 1185 // Create the idsig file for the APK 1186 String apkPath; 1187 if (hasAppPackage(EMPTY_AOSP_PACKAGE_NAME, android)) 1188 apkPath = getPathForPackage(EMPTY_AOSP_PACKAGE_NAME); 1189 else 1190 apkPath = getPathForPackage(EMPTY_PACKAGE_NAME); 1191 1192 final String idSigPath = TEST_ROOT + "idsig"; 1193 final String instanceImgPath = TEST_ROOT + "instance.img"; 1194 1195 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 1196 1197 List<String> cmd = 1198 new ArrayList<>( 1199 Arrays.asList( 1200 "adb", 1201 "-s", 1202 getDevice().getSerialNumber(), 1203 "shell", 1204 VIRT_APEX + "bin/vm", 1205 "run-app", 1206 "--debug " + DEBUG_LEVEL_FULL, 1207 "--console " + CONSOLE_PATH, 1208 "--payload-binary-name", 1209 "MicrodroidEmptyPayloadJniLib.so", 1210 apkPath, 1211 idSigPath, 1212 instanceImgPath)); 1213 if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) { 1214 cmd.add("--instance-id-file"); 1215 cmd.add(TEST_ROOT + "instance_id"); 1216 } 1217 1218 PipedInputStream pis = new PipedInputStream(); 1219 Process process = createRunUtil().runCmdInBackground(cmd, new PipedOutputStream(pis)); 1220 String bufferedInput = ""; 1221 1222 do { 1223 byte[] pipeBuffer = new byte[4096]; 1224 pis.read(pipeBuffer, 0, 4096); 1225 bufferedInput += new String(pipeBuffer); 1226 } while (!bufferedInput.contains("payload is ready")); 1227 1228 String consoleLog = getDevice().pullFileContents(CONSOLE_PATH); 1229 assertThat(consoleLog).contains("Hello Microdroid"); 1230 1231 process.destroy(); 1232 } 1233 1234 @Test 1235 public void testAllVbmetaUseSHA256() throws Exception { 1236 File virtApexDir = FileUtil.createTempDir("virt_apex"); 1237 // Pull the virt apex's etc/ directory (which contains images) 1238 File virtApexEtcDir = new File(virtApexDir, "etc"); 1239 // We need only etc/ directory for images 1240 assertWithMessage("Failed to mkdir " + virtApexEtcDir) 1241 .that(virtApexEtcDir.mkdirs()) 1242 .isTrue(); 1243 assertWithMessage("Failed to pull " + VIRT_APEX + "etc") 1244 .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)) 1245 .isTrue(); 1246 1247 checkHashAlgorithm(virtApexEtcDir); 1248 } 1249 1250 @Test 1251 @CddTest 1252 public void testNoAvfDebugPolicyInLockedDevice() throws Exception { 1253 ITestDevice device = getDevice(); 1254 1255 // Check device's locked state with ro.boot.verifiedbootstate. ro.boot.flash.locked 1256 // may not be set if ro.oem_unlock_supported is false. 1257 String lockProp = device.getProperty("ro.boot.verifiedbootstate"); 1258 assumeFalse("Unlocked devices may have AVF debug policy", lockProp.equals("orange")); 1259 1260 // Test that AVF debug policy doesn't exist. 1261 boolean hasDebugPolicy = device.doesFileExist("/proc/device-tree/avf/guest"); 1262 assertThat(hasDebugPolicy).isFalse(); 1263 } 1264 1265 private boolean isLz4(String path) throws Exception { 1266 File lz4tool = findTestFile("lz4"); 1267 CommandResult result = 1268 createRunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path); 1269 return result.getStatus() == CommandStatus.SUCCESS; 1270 } 1271 1272 private void decompressLz4(String inputPath, String outputPath) throws Exception { 1273 File lz4tool = findTestFile("lz4"); 1274 CommandResult result = 1275 createRunUtil() 1276 .runTimedCmd( 1277 5000, lz4tool.getAbsolutePath(), "-d", "-f", inputPath, outputPath); 1278 String out = result.getStdout(); 1279 String err = result.getStderr(); 1280 assertWithMessage( 1281 "lz4 image " 1282 + inputPath 1283 + " decompression failed." 1284 + "\n\tout: " 1285 + out 1286 + "\n\terr: " 1287 + err 1288 + "\n") 1289 .about(command_results()) 1290 .that(result) 1291 .isSuccess(); 1292 } 1293 1294 private String avbInfo(String image_path) throws Exception { 1295 if (isLz4(image_path)) { 1296 File decompressedImage = FileUtil.createTempFile("decompressed", ".img"); 1297 decompressedImage.deleteOnExit(); 1298 decompressLz4(image_path, decompressedImage.getAbsolutePath()); 1299 image_path = decompressedImage.getAbsolutePath(); 1300 } 1301 1302 File avbtool = findTestFile("avbtool"); 1303 List<String> command = 1304 Arrays.asList(avbtool.getAbsolutePath(), "info_image", "--image", image_path); 1305 CommandResult result = 1306 createRunUtil().runTimedCmd(10000, "/bin/bash", "-c", String.join(" ", command)); 1307 String out = result.getStdout(); 1308 String err = result.getStderr(); 1309 assertWithMessage( 1310 "Command " 1311 + command 1312 + " failed." 1313 + ":\n\tout: " 1314 + out 1315 + "\n\terr: " 1316 + err 1317 + "\n") 1318 .about(command_results()) 1319 .that(result) 1320 .isSuccess(); 1321 return out; 1322 } 1323 1324 private void checkHashAlgorithm(File virtApexEtcDir) throws Exception { 1325 List<String> images = 1326 Arrays.asList( 1327 // kernel image (contains descriptors from initrd(s) as well) 1328 "/fs/microdroid_kernel", 1329 // vbmeta partition (contains descriptors from vendor/system images) 1330 "/fs/microdroid_vbmeta.img"); 1331 1332 for (String path : images) { 1333 String info = avbInfo(virtApexEtcDir + path); 1334 Pattern pattern = Pattern.compile("Hash Algorithm:[ ]*(sha1|sha256)"); 1335 Matcher m = pattern.matcher(info); 1336 while (m.find()) { 1337 assertThat(m.group(1)).isEqualTo("sha256"); 1338 } 1339 } 1340 } 1341 1342 @Test 1343 @Parameters(method = "params") 1344 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 1345 @CddTest 1346 public void testDeviceAssignment(boolean protectedVm, String os) throws Exception { 1347 // Preconditions 1348 assumeKernelSupported(os); 1349 assumeVmTypeSupported(os, protectedVm); 1350 assumeVfioPlatformSupported(); 1351 1352 List<AssignableDevice> devices = getAssignableDevices(); 1353 assumeFalse("no assignable devices", devices.isEmpty()); 1354 1355 String dtSysfsPath = "/proc/device-tree/"; 1356 1357 // Try assign devices one by one 1358 for (AssignableDevice device : devices) { 1359 launchWithDeviceAssignment(device.node, protectedVm, os); 1360 1361 String dtPath = 1362 new CommandRunner(mMicrodroidDevice) 1363 .run("cat", dtSysfsPath + "__symbols__/" + device.dtbo_label); 1364 assertThat(dtPath).isNotEmpty(); 1365 1366 String resolvedDtPath = 1367 new CommandRunner(mMicrodroidDevice) 1368 .run("readlink", "-e", dtSysfsPath + dtPath); 1369 assertThat(resolvedDtPath).isNotEmpty(); 1370 1371 String allDevices = 1372 new CommandRunner(mMicrodroidDevice) 1373 .run("readlink", "-e", "/sys/bus/platform/devices/*/of_node"); 1374 assertThat(allDevices.split("\n")).asList().contains(resolvedDtPath); 1375 1376 getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice); 1377 mMicrodroidDevice = null; 1378 } 1379 } 1380 1381 private void launchWithDeviceAssignment(String device, boolean protectedVm, String os) 1382 throws Exception { 1383 Objects.requireNonNull(device); 1384 final String configPath = "assets/vm_config.json"; 1385 1386 MicrodroidBuilder microdroidBuilder = 1387 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1388 .debugLevel(DEBUG_LEVEL_FULL) 1389 .memoryMib(minMemorySize()) 1390 .cpuTopology("match_host") 1391 .protectedVm(protectedVm) 1392 .addAssignableDevice(device); 1393 if (getAndroidDevice().getApiLevel() >= 36) { 1394 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 1395 } 1396 1397 mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice()); 1398 assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue(); 1399 assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue(); 1400 } 1401 1402 @Test 1403 @CddTest 1404 @GmsTest(requirements = {"GMS-3-7.1-001.002"}) 1405 public void testOsVersions() throws Exception { 1406 for (String os : getSupportedOSList()) { 1407 assertWithMessage("Unknown OS \"%s\"", os).that(SUPPORTED_OSES.values()).contains(os); 1408 } 1409 } 1410 1411 @Test 1412 @Parameters(method = "params") 1413 @TestCaseName("{method}_protectedVm_{0}_os_{1}") 1414 public void testHugePages(boolean protectedVm, String os) throws Exception { 1415 // Preconditions 1416 assumeKernelSupported(os); 1417 assumeVmTypeSupported(os, protectedVm); 1418 1419 ITestDevice device = getDevice(); 1420 boolean disableRoot = !device.isAdbRoot(); 1421 CommandRunner android = new CommandRunner(device); 1422 1423 final String SHMEM_ENABLED_PATH = "/sys/kernel/mm/transparent_hugepage/shmem_enabled"; 1424 String thpShmemStr = android.run("cat", SHMEM_ENABLED_PATH); 1425 1426 assumeFalse("shmem already enabled, skip", thpShmemStr.contains("[advise]")); 1427 assumeTrue("Unsupported shmem, skip", thpShmemStr.contains("[never]")); 1428 1429 device.enableAdbRoot(); 1430 assumeTrue("adb root is not enabled", device.isAdbRoot()); 1431 android.run("echo advise > " + SHMEM_ENABLED_PATH); 1432 1433 final String configPath = "assets/vm_config.json"; 1434 MicrodroidBuilder microdroidBuilder = 1435 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1436 .debugLevel(DEBUG_LEVEL_FULL) 1437 .memoryMib(minMemorySize()) 1438 .cpuTopology("match_host") 1439 .protectedVm(protectedVm) 1440 .hugePages(true) 1441 .name("test_huge_pages"); 1442 if (getAndroidDevice().getApiLevel() >= 36) { 1443 microdroidBuilder.os(SUPPORTED_OSES.get(os)); 1444 } 1445 1446 mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice()); 1447 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 1448 1449 android.run("echo never >" + SHMEM_ENABLED_PATH); 1450 if (disableRoot) { 1451 device.disableAdbRoot(); 1452 } 1453 } 1454 1455 @Before 1456 public void setUp() throws Exception { 1457 assumeDeviceIsCapable(getDevice()); 1458 mMetricPrefix = getMetricPrefix() + "microdroid/"; 1459 mMicrodroidDevice = null; 1460 1461 prepareVirtualizationTestSetup(getDevice()); 1462 1463 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ false); 1464 1465 new CommandRunner(getDevice()) 1466 .tryRun( 1467 "pm", 1468 "grant", 1469 SHELL_PACKAGE_NAME, 1470 "android.permission.USE_CUSTOM_VIRTUAL_MACHINE"); 1471 } 1472 1473 @After 1474 public void shutdown() throws Exception { 1475 if (mMicrodroidDevice != null) { 1476 getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice); 1477 } 1478 1479 cleanUpVirtualizationTestSetup(getDevice()); 1480 1481 archiveLogThenDelete( 1482 mTestLogs, getDevice(), LOG_PATH, "vm.log-" + mTestName.getMethodName()); 1483 1484 getDevice().uninstallPackage(PACKAGE_NAME); 1485 } 1486 1487 private void assumeVfioPlatformSupported() throws Exception { 1488 TestDevice device = getAndroidDevice(); 1489 assumeTrue( 1490 "Test skipped because VFIO platform is not supported.", 1491 device.doesFileExist("/dev/vfio/vfio") 1492 && device.doesFileExist("/sys/bus/platform/drivers/vfio-platform")); 1493 } 1494 1495 private void ensureUpdatableVmSupported() throws DeviceNotAvailableException { 1496 if (PropertyUtil.getVsrApiLevel(getAndroidDevice()) >= 202504) { 1497 assertTrue( 1498 "Missing Updatable VM support, have you declared Secretkeeper interface?", 1499 isUpdatableVmSupported()); 1500 } else { 1501 assumeTrue( 1502 "Vendor API lower than 202504 may not support Updatable VM", 1503 isUpdatableVmSupported()); 1504 } 1505 } 1506 1507 // The TradeFed Dockerfile sets LD_LIBRARY_PATH to a directory with an older libc++.so, which 1508 // breaks binaries that are linked against a newer libc++.so. Binaries commonly use DT_RUNPATH 1509 // to find an adjacent libc++.so (e.g. `$ORIGIN/../lib64`), but LD_LIBRARY_PATH overrides 1510 // DT_RUNPATH, so clear LD_LIBRARY_PATH. See b/332593805 and b/333782216. 1511 private static RunUtil createRunUtil() { 1512 RunUtil runUtil = new RunUtil(); 1513 runUtil.unsetEnvVariable("LD_LIBRARY_PATH"); 1514 return runUtil; 1515 } 1516 1517 private void assumeArm64Supported() throws Exception { 1518 CommandRunner android = new CommandRunner(getDevice()); 1519 String abi = android.run("getprop", "ro.product.cpu.abi"); 1520 assertThat(abi).isNotEmpty(); 1521 assumeTrue("Skipping test as the architecture is not supported", abi.startsWith("arm64")); 1522 } 1523 } 1524