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