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 import com.android.tradefed.device.DeviceRuntimeException; 23 24 import static com.google.common.truth.Truth.assertThat; 25 import static com.google.common.truth.Truth.assertWithMessage; 26 27 import static org.hamcrest.CoreMatchers.containsString; 28 import static org.junit.Assert.assertThat; 29 import static org.junit.Assert.assertThrows; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assume.assumeFalse; 32 import static org.junit.Assume.assumeTrue; 33 34 import static java.util.stream.Collectors.toList; 35 36 import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters; 37 import android.cts.host.utils.DeviceJUnit4Parameterized; 38 import android.cts.statsdatom.lib.ConfigUtils; 39 import android.cts.statsdatom.lib.ReportUtils; 40 41 import com.android.compatibility.common.util.CddTest; 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.ITestDevice; 49 import com.android.tradefed.device.TestDevice; 50 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics; 51 import com.android.tradefed.util.CommandResult; 52 import com.android.tradefed.util.CommandStatus; 53 import com.android.tradefed.util.FileUtil; 54 import com.android.tradefed.util.RunUtil; 55 import com.android.tradefed.util.xml.AbstractXmlParser; 56 import com.android.virt.PayloadMetadata; 57 58 import org.json.JSONArray; 59 import org.json.JSONObject; 60 import org.junit.After; 61 import org.junit.Before; 62 import org.junit.Ignore; 63 import org.junit.Rule; 64 import org.junit.Test; 65 import org.junit.rules.TestName; 66 import org.junit.runner.RunWith; 67 import org.junit.runners.Parameterized; 68 import org.junit.runners.Parameterized.UseParametersRunnerFactory; 69 import org.xml.sax.Attributes; 70 import org.xml.sax.helpers.DefaultHandler; 71 72 import java.io.ByteArrayInputStream; 73 import java.io.File; 74 import java.io.PipedInputStream; 75 import java.io.PipedOutputStream; 76 import java.util.ArrayList; 77 import java.util.Arrays; 78 import java.util.Collection; 79 import java.util.Collections; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.concurrent.Callable; 83 import java.util.concurrent.TimeUnit; 84 import java.util.function.Function; 85 import java.util.regex.Matcher; 86 import java.util.regex.Pattern; 87 import java.util.stream.Collectors; 88 import java.util.Objects; 89 90 @RunWith(DeviceJUnit4Parameterized.class) 91 @UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.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 SHELL_PACKAGE_NAME = "com.android.shell"; 97 private static final String VIRT_APEX = "/apex/com.android.virt/"; 98 private static final String INSTANCE_IMG = TEST_ROOT + "instance.img"; 99 private static final String INSTANCE_ID_FILE = TEST_ROOT + "instance_id"; 100 101 private static final int MIN_MEM_ARM64 = 170; 102 private static final int MIN_MEM_X86_64 = 196; 103 104 private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds 105 106 private static class VmInfo { 107 final Process mProcess; 108 VmInfo(Process process)109 VmInfo(Process process) { 110 mProcess = process; 111 } 112 } 113 114 @Parameterized.Parameters(name = "protectedVm={0},gki={1}") params()115 public static Collection<Object[]> params() { 116 List<Object[]> ret = new ArrayList<>(); 117 ret.add(new Object[] {true /* protectedVm */, null /* use microdroid kernel */}); 118 ret.add(new Object[] {false /* protectedVm */, null /* use microdroid kernel */}); 119 // TODO(b/302465542): run only the latest GKI on presubmit to reduce running time 120 for (String gki : SUPPORTED_GKI_VERSIONS) { 121 ret.add(new Object[] {true /* protectedVm */, gki}); 122 ret.add(new Object[] {false /* protectedVm */, gki}); 123 } 124 return ret; 125 } 126 127 @Parameterized.Parameter(0) 128 public boolean mProtectedVm; 129 130 @Parameterized.Parameter(1) 131 public String mGki; 132 133 private String mOs; 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 throws Exception { 295 CommandRunner android = new CommandRunner(getDevice()); 296 297 File virtApexDir = FileUtil.createTempDir("virt_apex"); 298 299 // Pull the virt apex's etc/ directory (which contains images and microdroid.json) 300 File virtApexEtcDir = new File(virtApexDir, "etc"); 301 // We need only etc/ directory for images 302 assertWithMessage("Failed to mkdir " + virtApexEtcDir) 303 .that(virtApexEtcDir.mkdirs()) 304 .isTrue(); 305 assertWithMessage("Failed to pull " + VIRT_APEX + "etc") 306 .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)) 307 .isTrue(); 308 309 resignVirtApex(virtApexDir, key, keyOverrides, updateBootconfigs); 310 311 // Push back re-signed virt APEX contents and updated microdroid.json 312 getDevice().pushDir(virtApexDir, TEST_ROOT); 313 314 // Create the idsig file for the APK 315 final String apkPath = getPathForPackage(PACKAGE_NAME); 316 final String idSigPath = TEST_ROOT + "idsig"; 317 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 318 319 // Create the instance image for the VM 320 final String instanceImgPath = TEST_ROOT + "instance.img"; 321 android.run( 322 VIRT_APEX + "bin/vm", 323 "create-partition", 324 "--type instance", 325 instanceImgPath, 326 Integer.toString(10 * 1024 * 1024)); 327 328 // payload-metadata is created on device 329 final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img"; 330 331 // Load /apex/apex-info-list.xml to get paths to APEXes required for the VM. 332 ActiveApexInfoList list = getActiveApexInfoList(); 333 334 // Since Java APP can't start a VM with a custom image, here, we start a VM using `vm run` 335 // command with a VM Raw config which is equiv. to what virtualizationservice creates with 336 // a VM App config. 337 // 338 // 1. use etc/microdroid.json as base 339 // 2. add partitions: bootconfig, vbmeta, instance image 340 // 3. add a payload image disk with 341 // - payload-metadata 342 // - apexes 343 // - test apk 344 // - its idsig 345 346 // Load etc/microdroid.json 347 File microdroidConfigFile = new File(virtApexEtcDir, mOs + ".json"); 348 JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile)); 349 350 // Replace paths so that the config uses re-signed images from TEST_ROOT 351 config.put("kernel", config.getString("kernel").replace(VIRT_APEX, TEST_ROOT)); 352 JSONArray disks = config.getJSONArray("disks"); 353 for (int diskIndex = 0; diskIndex < disks.length(); diskIndex++) { 354 JSONObject disk = disks.getJSONObject(diskIndex); 355 JSONArray partitions = disk.getJSONArray("partitions"); 356 for (int partIndex = 0; partIndex < partitions.length(); partIndex++) { 357 JSONObject part = partitions.getJSONObject(partIndex); 358 part.put("path", part.getString("path").replace(VIRT_APEX, TEST_ROOT)); 359 } 360 } 361 362 // Add partitions to the second disk 363 final String initrdPath = TEST_ROOT + "etc/" + mOs + "_initrd_debuggable.img"; 364 config.put("initrd", initrdPath); 365 // Add instance image as a partition in disks[1] 366 disks.put( 367 new JSONObject() 368 .put("writable", true) 369 .put( 370 "partitions", 371 new JSONArray().put(newPartition("vm-instance", instanceImgPath)))); 372 // Add payload image disk with partitions: 373 // - payload-metadata 374 // - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional) 375 // - apk and idsig 376 List<ActiveApexInfo> apexesForVm = new ArrayList<>(); 377 apexesForVm.add(list.get("com.android.os.statsd")); 378 apexesForVm.add(list.get("com.android.adbd")); 379 apexesForVm.addAll(list.getSharedLibApexes()); 380 381 final JSONArray partitions = new JSONArray(); 382 partitions.put(newPartition("payload-metadata", payloadMetadataPath)); 383 for (ActiveApexInfo apex : apexesForVm) { 384 partitions.put(newPartition(apex.name, apex.path)); 385 } 386 partitions 387 .put(newPartition("microdroid-apk", apkPath)) 388 .put(newPartition("microdroid-apk-idsig", idSigPath)); 389 disks.put(new JSONObject().put("writable", false).put("partitions", partitions)); 390 391 final File localPayloadMetadata = new File(virtApexDir, "payload-metadata.img"); 392 createPayloadMetadata(apexesForVm, localPayloadMetadata); 393 getDevice().pushFile(localPayloadMetadata, payloadMetadataPath); 394 395 config.put("protected", isProtected); 396 397 // Write updated raw config 398 final String configPath = TEST_ROOT + "raw_config.json"; 399 getDevice().pushString(config.toString(), configPath); 400 401 List<String> args = 402 Arrays.asList( 403 "adb", 404 "-s", 405 getDevice().getSerialNumber(), 406 "shell", 407 VIRT_APEX + "bin/vm run", 408 "--console " + CONSOLE_PATH, 409 "--log " + LOG_PATH, 410 configPath); 411 412 PipedInputStream pis = new PipedInputStream(); 413 Process process = createRunUtil().runCmdInBackground(args, new PipedOutputStream(pis)); 414 return new VmInfo(process); 415 } 416 417 @Test 418 @CddTest 419 public void UpgradedPackageIsAcceptedWithSecretkeeper() throws Exception { 420 assumeUpdatableVmSupported(); 421 getDevice().uninstallPackage(PACKAGE_NAME); 422 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ true); 423 ensureMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 424 425 getDevice().uninstallPackage(PACKAGE_NAME); 426 cleanUpVirtualizationTestSetup(getDevice()); 427 // Install the updated version of app (versionCode 6) 428 getDevice().installPackage(findTestFile(APK_UPDATED_NAME), /* reinstall= */ true); 429 ensureMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 430 } 431 432 @Test 433 @CddTest 434 public void DowngradedPackageIsRejectedProtectedVm() throws Exception { 435 assumeProtectedVm(); // Rollback protection is provided only for protected VM. 436 437 // Install the upgraded version (v6) 438 getDevice().uninstallPackage(PACKAGE_NAME); 439 getDevice().installPackage(findTestFile(APK_UPDATED_NAME), /* reinstall= */ true); 440 ensureMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG); 441 442 getDevice().uninstallPackage(PACKAGE_NAME); 443 cleanUpVirtualizationTestSetup(getDevice()); 444 // Install the older version (v5) 445 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ true); 446 447 assertThrows( 448 "pVM must fail to boot with downgraded payload apk", 449 DeviceRuntimeException.class, 450 () -> ensureMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG)); 451 } 452 453 private void ensureMicrodroidBootsSuccessfully(String instanceIdPath, String instanceImgPath) 454 throws DeviceNotAvailableException { 455 final String configPath = "assets/vm_config.json"; 456 ITestDevice microdroid = null; 457 int timeout = 30000; // 30 seconds 458 try { 459 microdroid = 460 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 461 .debugLevel("full") 462 .memoryMib(minMemorySize()) 463 .cpuTopology("match_host") 464 .protectedVm(mProtectedVm) 465 .instanceIdFile(instanceIdPath) 466 .instanceImgFile(instanceImgPath) 467 .setAdbConnectTimeoutMs(timeout) 468 .build(getAndroidDevice()); 469 assertThat(microdroid.waitForBootComplete(timeout)).isTrue(); 470 assertThat(microdroid.enableAdbRoot()).isTrue(); 471 } finally { 472 if (microdroid != null) { 473 getAndroidDevice().shutdownMicrodroid(microdroid); 474 } 475 } 476 } 477 478 @Test 479 @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"}) 480 public void protectedVmRunsPvmfw() throws Exception { 481 // Arrange 482 assumeProtectedVm(); 483 final String configPath = "assets/vm_config_apex.json"; 484 485 // Act 486 mMicrodroidDevice = 487 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 488 .debugLevel("full") 489 .memoryMib(minMemorySize()) 490 .cpuTopology("match_host") 491 .protectedVm(true) 492 .gki(mGki) 493 .build(getAndroidDevice()); 494 495 // Assert 496 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 497 String consoleLog = getDevice().pullFileContents(TRADEFED_CONSOLE_PATH); 498 assertWithMessage("Failed to verify that pvmfw started") 499 .that(consoleLog) 500 .contains("pVM firmware"); 501 assertWithMessage("pvmfw failed to start kernel") 502 .that(consoleLog) 503 .contains("Starting payload..."); 504 // TODO(b/260994818): Investigate the feasibility of checking DeathReason. 505 } 506 507 @Test 508 @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"}) 509 public void protectedVmWithImageSignedWithDifferentKeyFailsToVerifyPayload() throws Exception { 510 // Arrange 511 assumeProtectedVm(); 512 File key = findTestFile("test.com.android.virt.pem"); 513 514 // Act 515 VmInfo vmInfo = 516 runMicrodroidWithResignedImages( 517 key, 518 /* keyOverrides= */ Map.of(), 519 /* isProtected= */ true, 520 /* updateBootconfigs= */ true); 521 522 // Assert 523 vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS); 524 String consoleLog = getDevice().pullFileContents(CONSOLE_PATH); 525 assertWithMessage("pvmfw should start").that(consoleLog).contains("pVM firmware"); 526 assertWithMessage("pvmfw should fail to verify the payload") 527 .that(consoleLog) 528 .contains("Failed to verify the payload"); 529 vmInfo.mProcess.destroy(); 530 } 531 532 @Test 533 @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"}) 534 public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey() 535 throws Exception { 536 assumeNonProtectedVm(); 537 File key = findTestFile("test.com.android.virt.pem"); 538 Map<String, File> keyOverrides = Map.of(); 539 VmInfo vmInfo = 540 runMicrodroidWithResignedImages( 541 key, keyOverrides, /* isProtected= */ false, /* updateBootconfigs= */ true); 542 assertThatEventually( 543 100000, 544 () -> 545 getDevice().pullFileContents(CONSOLE_PATH) 546 + getDevice().pullFileContents(LOG_PATH), 547 containsString("boot completed, time to run payload")); 548 549 vmInfo.mProcess.destroy(); 550 } 551 552 @Test 553 @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"}) 554 public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig() throws Exception { 555 // protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case. 556 assumeNonProtectedVm(); 557 // Sign everything with key1 except vbmeta 558 File key = findTestFile("test.com.android.virt.pem"); 559 // To be able to stop it, it should be a daemon. 560 VmInfo vmInfo = 561 runMicrodroidWithResignedImages( 562 key, Map.of(), /* isProtected= */ false, /* updateBootconfigs= */ false); 563 // Wait so that init can print errors to console (time in cuttlefish >> in real device) 564 assertThatEventually( 565 100000, 566 () -> getDevice().pullFileContents(CONSOLE_PATH), 567 containsString("init: [libfs_avb] Failed to verify vbmeta digest")); 568 vmInfo.mProcess.destroy(); 569 } 570 571 private void waitForCrosvmExit(CommandRunner android, String testStartTime) throws Exception { 572 // TODO: improve crosvm exit check. b/258848245 573 android.runWithTimeout( 574 15000, 575 "logcat", 576 "-m", 577 "1", 578 "-e", 579 "'virtualizationmanager::crosvm.*exited with status exit status:'", 580 "-T", 581 "'" + testStartTime + "'"); 582 } 583 584 private boolean isTombstoneReceivedFromHostLogcat(String testStartTime) throws Exception { 585 // Note this method relies on logcat values being printed by the receiver on host 586 // userspace crash log: virtualizationservice/src/aidl.rs 587 // kernel ramdump log: virtualizationmanager/src/crosvm.rs 588 String ramdumpRegex = 589 "Received [0-9]+ bytes from guest & wrote to tombstone file|" 590 + "Ramdump \"[^ ]+/ramdump\" sent to tombstoned"; 591 592 String result = 593 tryRunOnHost( 594 "timeout", 595 "3s", 596 "adb", 597 "-s", 598 getDevice().getSerialNumber(), 599 "logcat", 600 "-m", 601 "1", 602 "-e", 603 ramdumpRegex, 604 "-T", 605 testStartTime); 606 return !result.trim().isEmpty(); 607 } 608 609 private boolean isTombstoneGeneratedWithCmd( 610 boolean protectedVm, String configPath, String... crashCommand) throws Exception { 611 CommandRunner android = new CommandRunner(getDevice()); 612 String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'"); 613 614 mMicrodroidDevice = 615 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 616 .debugLevel("full") 617 .memoryMib(minMemorySize()) 618 .cpuTopology("match_host") 619 .protectedVm(protectedVm) 620 .gki(mGki) 621 .build(getAndroidDevice()); 622 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 623 mMicrodroidDevice.enableAdbRoot(); 624 625 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 626 // can crash in the middle of crashCommand; fail is ok 627 microdroid.tryRun(crashCommand); 628 629 // check until microdroid is shut down 630 waitForCrosvmExit(android, testStartTime); 631 632 return isTombstoneReceivedFromHostLogcat(testStartTime); 633 } 634 635 @Test 636 public void testTombstonesAreGeneratedUponUserspaceCrash() throws Exception { 637 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 638 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 639 assertThat( 640 isTombstoneGeneratedWithCmd( 641 mProtectedVm, 642 "assets/vm_config.json", 643 "kill", 644 "-SIGSEGV", 645 "$(pidof microdroid_launcher)")) 646 .isTrue(); 647 } 648 649 @Test 650 public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash() throws Exception { 651 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 652 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 653 assertThat( 654 isTombstoneGeneratedWithCmd( 655 mProtectedVm, 656 "assets/vm_config_no_tombstone.json", 657 "kill", 658 "-SIGSEGV", 659 "$(pidof microdroid_launcher)")) 660 .isFalse(); 661 } 662 663 @Test 664 @Ignore("b/341087884") // TODO(b/341087884): fix & re-enable 665 public void testTombstonesAreGeneratedUponKernelCrash() throws Exception { 666 assumeFalse("Cuttlefish is not supported", isCuttlefish()); 667 assumeFalse("Skipping test because ramdump is disabled on user build", isUserBuild()); 668 assertThat( 669 isTombstoneGeneratedWithCmd( 670 mProtectedVm, 671 "assets/vm_config.json", 672 "echo", 673 "c", 674 ">", 675 "/proc/sysrq-trigger")) 676 .isTrue(); 677 } 678 679 private boolean isTombstoneGeneratedWithVmRunApp( 680 boolean protectedVm, boolean debuggable, String... additionalArgs) throws Exception { 681 // we can't use microdroid builder as it wants ADB connection (debuggable) 682 CommandRunner android = new CommandRunner(getDevice()); 683 String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'"); 684 685 android.run("rm", "-rf", TEST_ROOT + "*"); 686 android.run("mkdir", "-p", TEST_ROOT + "*"); 687 688 final String apkPath = getPathForPackage(PACKAGE_NAME); 689 final String idsigPath = TEST_ROOT + "idsig"; 690 final String instanceImgPath = TEST_ROOT + "instance.img"; 691 final String instanceIdPath = TEST_ROOT + "instance_id"; 692 List<String> cmd = 693 new ArrayList<>( 694 Arrays.asList( 695 VIRT_APEX + "bin/vm", 696 "run-app", 697 "--debug", 698 debuggable ? "full" : "none", 699 apkPath, 700 idsigPath, 701 instanceImgPath)); 702 if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) { 703 cmd.add("--instance-id-file"); 704 cmd.add(instanceIdPath); 705 } 706 ; 707 if (protectedVm) { 708 cmd.add("--protected"); 709 } 710 if (mGki != null) { 711 cmd.add("--gki"); 712 cmd.add(mGki); 713 } 714 Collections.addAll(cmd, additionalArgs); 715 716 android.run(cmd.toArray(new String[0])); 717 return isTombstoneReceivedFromHostLogcat(testStartTime); 718 } 719 720 private boolean isTombstoneGeneratedWithCrashPayload(boolean protectedVm, boolean debuggable) 721 throws Exception { 722 return isTombstoneGeneratedWithVmRunApp( 723 protectedVm, debuggable, "--payload-binary-name", "MicrodroidCrashNativeLib.so"); 724 } 725 726 @Test 727 public void testTombstonesAreGeneratedWithCrashPayload() throws Exception { 728 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 729 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 730 assertThat(isTombstoneGeneratedWithCrashPayload(mProtectedVm, /* debuggable= */ true)) 731 .isTrue(); 732 } 733 734 @Test 735 public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggable() throws Exception { 736 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 737 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 738 assertThat(isTombstoneGeneratedWithCrashPayload(mProtectedVm, /* debuggable= */ false)) 739 .isFalse(); 740 } 741 742 private boolean isTombstoneGeneratedWithCrashConfig(boolean protectedVm, boolean debuggable) 743 throws Exception { 744 return isTombstoneGeneratedWithVmRunApp( 745 protectedVm, debuggable, "--config-path", "assets/vm_config_crash.json"); 746 } 747 748 @Test 749 public void testTombstonesAreGeneratedWithCrashConfig() throws Exception { 750 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 751 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 752 assertThat(isTombstoneGeneratedWithCrashConfig(mProtectedVm, /* debuggable= */ true)) 753 .isTrue(); 754 } 755 756 @Test 757 public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggable() throws Exception { 758 // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid. 759 assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan()); 760 assertThat(isTombstoneGeneratedWithCrashConfig(mProtectedVm, /* debuggable= */ false)) 761 .isFalse(); 762 } 763 764 @Test 765 public void testTelemetryPushedAtoms() throws Exception { 766 // Reset statsd config and report before the test 767 ConfigUtils.removeConfig(getDevice()); 768 ReportUtils.clearReports(getDevice()); 769 770 // Setup statsd config 771 int[] atomIds = { 772 AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER, 773 AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER, 774 AtomsProto.Atom.VM_EXITED_FIELD_NUMBER, 775 }; 776 ConfigUtils.uploadConfigForPushedAtoms(getDevice(), PACKAGE_NAME, atomIds); 777 778 // Create VM with microdroid 779 TestDevice device = getAndroidDevice(); 780 final String configPath = "assets/vm_config_apex.json"; // path inside the APK 781 ITestDevice microdroid = 782 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 783 .debugLevel("full") 784 .memoryMib(minMemorySize()) 785 .cpuTopology("match_host") 786 .protectedVm(mProtectedVm) 787 .gki(mGki) 788 .build(device); 789 microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 790 device.shutdownMicrodroid(microdroid); 791 792 // Try to collect atoms for 60000 milliseconds. 793 List<StatsLog.EventMetricData> data = new ArrayList<>(); 794 long start = System.currentTimeMillis(); 795 while ((System.currentTimeMillis() - start < 60000) && data.size() < 3) { 796 data.addAll(ReportUtils.getEventMetricDataList(getDevice())); 797 Thread.sleep(500); 798 } 799 assertThat( 800 data.stream() 801 .map(x -> x.getAtom().getPushedCase().getNumber()) 802 .collect(Collectors.toList())) 803 .containsExactly( 804 AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER, 805 AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER, 806 AtomsProto.Atom.VM_EXITED_FIELD_NUMBER) 807 .inOrder(); 808 809 // Check VmCreationRequested atom 810 AtomsProto.VmCreationRequested atomVmCreationRequested = 811 data.get(0).getAtom().getVmCreationRequested(); 812 if (isPkvmHypervisor()) { 813 assertThat(atomVmCreationRequested.getHypervisor()) 814 .isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM); 815 } 816 assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(mProtectedVm); 817 assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue(); 818 assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0); 819 assertThat(atomVmCreationRequested.getVmIdentifier()).isEqualTo("VmRunApp"); 820 assertThat(atomVmCreationRequested.getConfigType()) 821 .isEqualTo(AtomsProto.VmCreationRequested.ConfigType.VIRTUAL_MACHINE_APP_CONFIG); 822 assertThat(atomVmCreationRequested.getNumCpus()).isEqualTo(getDeviceNumCpus(device)); 823 assertThat(atomVmCreationRequested.getMemoryMib()).isEqualTo(minMemorySize()); 824 assertThat(atomVmCreationRequested.getApexes()) 825 .isEqualTo("com.android.art:com.android.compos:com.android.sdkext"); 826 827 // Check VmBooted atom 828 AtomsProto.VmBooted atomVmBooted = data.get(1).getAtom().getVmBooted(); 829 assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("VmRunApp"); 830 831 // Check VmExited atom 832 AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited(); 833 assertThat(atomVmExited.getVmIdentifier()).isEqualTo("VmRunApp"); 834 assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED); 835 assertThat(atomVmExited.getExitSignal()).isEqualTo(9); 836 // In CPU & memory related fields, check whether positive values are collected or not. 837 if (isPkvmHypervisor()) { 838 // Guest Time may not be updated on other hypervisors. 839 // Checking only if the hypervisor is PKVM. 840 assertThat(atomVmExited.getGuestTimeMillis()).isGreaterThan(0); 841 } 842 assertThat(atomVmExited.getRssVmKb()).isGreaterThan(0); 843 assertThat(atomVmExited.getRssCrosvmKb()).isGreaterThan(0); 844 845 // Check UID and elapsed_time by comparing each other. 846 assertThat(atomVmBooted.getUid()).isEqualTo(atomVmCreationRequested.getUid()); 847 assertThat(atomVmExited.getUid()).isEqualTo(atomVmCreationRequested.getUid()); 848 assertThat(atomVmBooted.getElapsedTimeMillis()) 849 .isLessThan(atomVmExited.getElapsedTimeMillis()); 850 } 851 852 private void testMicrodroidBootsWithBuilder(MicrodroidBuilder builder) throws Exception { 853 CommandRunner android = new CommandRunner(getDevice()); 854 855 mMicrodroidDevice = builder.build(getAndroidDevice()); 856 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 857 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 858 859 String vmList = android.run("/apex/com.android.virt/bin/vm list"); 860 assertThat(vmList).contains("requesterUid: " + android.run("id -u")); 861 862 // Test writing to /data partition 863 microdroid.run("echo MicrodroidTest > /data/local/tmp/test.txt"); 864 assertThat(microdroid.run("cat /data/local/tmp/test.txt")).isEqualTo("MicrodroidTest"); 865 866 // Check if the APK & its idsig partitions exist 867 final String apkPartition = "/dev/block/by-name/microdroid-apk"; 868 assertThat(microdroid.run("ls", apkPartition)).isEqualTo(apkPartition); 869 final String apkIdsigPartition = "/dev/block/by-name/microdroid-apk-idsig"; 870 assertThat(microdroid.run("ls", apkIdsigPartition)).isEqualTo(apkIdsigPartition); 871 // Check the vm-instance partition as well 872 final String vmInstancePartition = "/dev/block/by-name/vm-instance"; 873 assertThat(microdroid.run("ls", vmInstancePartition)).isEqualTo(vmInstancePartition); 874 875 // Check if the native library in the APK is has correct filesystem info 876 final String[] abis = microdroid.run("getprop", "ro.product.cpu.abilist").split(","); 877 assertWithMessage("Incorrect ABI list").that(abis).hasLength(1); 878 879 // Check that no denials have happened so far 880 String consoleText = getDevice().pullFileContents(TRADEFED_CONSOLE_PATH); 881 assertWithMessage("Console output shouldn't be empty").that(consoleText).isNotEmpty(); 882 String logText = getDevice().pullFileContents(TRADEFED_LOG_PATH); 883 assertWithMessage("Log output shouldn't be empty").that(logText).isNotEmpty(); 884 885 assertWithMessage("Unexpected denials during VM boot") 886 .that(consoleText + logText) 887 .doesNotContainMatch("avc:\\s+denied"); 888 889 assertThat(getDeviceNumCpus(microdroid)).isEqualTo(getDeviceNumCpus(android)); 890 891 // Check that selinux is enabled 892 assertWithMessage("SELinux should be in enforcing mode") 893 .that(microdroid.run("getenforce")) 894 .isEqualTo("Enforcing"); 895 896 // TODO(b/176805428): adb is broken for nested VM 897 if (!isCuttlefish()) { 898 // Check neverallow rules on microdroid 899 File policyFile = mMicrodroidDevice.pullFile("/sys/fs/selinux/policy"); 900 File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf"); 901 File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze"); 902 903 CommandResult result = 904 createRunUtil() 905 .runTimedCmd( 906 10000, 907 sepolicyAnalyzeBin.getPath(), 908 policyFile.getPath(), 909 "neverallow", 910 "-w", 911 "-f", 912 generalPolicyConfFile.getPath()); 913 assertWithMessage("neverallow check failed: " + result.getStderr().trim()) 914 .about(command_results()) 915 .that(result) 916 .isSuccess(); 917 } 918 } 919 920 @Test 921 @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"}) 922 public void testMicrodroidBoots() throws Exception { 923 final String configPath = "assets/vm_config.json"; // path inside the APK 924 testMicrodroidBootsWithBuilder( 925 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 926 .debugLevel("full") 927 .memoryMib(minMemorySize()) 928 .cpuTopology("match_host") 929 .protectedVm(mProtectedVm) 930 .gki(mGki)); 931 } 932 933 @Test 934 public void testMicrodroidRamUsage() throws Exception { 935 final String configPath = "assets/vm_config.json"; 936 mMicrodroidDevice = 937 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 938 .debugLevel("full") 939 .memoryMib(minMemorySize()) 940 .cpuTopology("match_host") 941 .protectedVm(mProtectedVm) 942 .gki(mGki) 943 .build(getAndroidDevice()); 944 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 945 mMicrodroidDevice.enableAdbRoot(); 946 947 CommandRunner microdroid = new CommandRunner(mMicrodroidDevice); 948 Function<String, String> microdroidExec = 949 (cmd) -> { 950 try { 951 return microdroid.run(cmd); 952 } catch (Exception ex) { 953 throw new IllegalStateException(ex); 954 } 955 }; 956 957 for (Map.Entry<String, Long> stat : 958 ProcessUtil.getProcessMemoryMap(microdroidExec).entrySet()) { 959 mMetrics.addTestMetric( 960 mMetricPrefix + "meminfo/" + stat.getKey().toLowerCase(), 961 stat.getValue().toString()); 962 } 963 964 for (Map.Entry<Integer, String> proc : 965 ProcessUtil.getProcessMap(microdroidExec).entrySet()) { 966 for (Map.Entry<String, Long> stat : 967 ProcessUtil.getProcessSmapsRollup(proc.getKey(), microdroidExec).entrySet()) { 968 String name = stat.getKey().toLowerCase(); 969 mMetrics.addTestMetric( 970 mMetricPrefix + "smaps/" + name + "/" + proc.getValue(), 971 stat.getValue().toString()); 972 } 973 } 974 } 975 976 @Test 977 public void testPathToBinaryIsRejected() throws Exception { 978 CommandRunner android = new CommandRunner(getDevice()); 979 980 // Create the idsig file for the APK 981 final String apkPath = getPathForPackage(PACKAGE_NAME); 982 final String idSigPath = TEST_ROOT + "idsig"; 983 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 984 // Create the instance image for the VM 985 final String instanceImgPath = TEST_ROOT + "instance.img"; 986 android.run( 987 VIRT_APEX + "bin/vm", 988 "create-partition", 989 "--type instance", 990 instanceImgPath, 991 Integer.toString(10 * 1024 * 1024)); 992 993 List<String> cmd = 994 new ArrayList<>( 995 Arrays.asList( 996 VIRT_APEX + "bin/vm", 997 "run-app", 998 "--payload-binary-name", 999 "./MicrodroidTestNativeLib.so", 1000 apkPath, 1001 idSigPath, 1002 instanceImgPath)); 1003 if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) { 1004 cmd.add("--instance-id-file"); 1005 cmd.add(TEST_ROOT + "instance_id"); 1006 } 1007 1008 final String ret = android.runForResult(String.join(" ", cmd)).getStderr().trim(); 1009 1010 assertThat(ret).contains("Payload binary name must not specify a path"); 1011 } 1012 1013 @Test 1014 @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"}) 1015 public void testAllVbmetaUseSHA256() throws Exception { 1016 File virtApexDir = FileUtil.createTempDir("virt_apex"); 1017 // Pull the virt apex's etc/ directory (which contains images) 1018 File virtApexEtcDir = new File(virtApexDir, "etc"); 1019 // We need only etc/ directory for images 1020 assertWithMessage("Failed to mkdir " + virtApexEtcDir) 1021 .that(virtApexEtcDir.mkdirs()) 1022 .isTrue(); 1023 assertWithMessage("Failed to pull " + VIRT_APEX + "etc") 1024 .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)) 1025 .isTrue(); 1026 1027 checkHashAlgorithm(virtApexEtcDir); 1028 } 1029 1030 @Test 1031 @CddTest 1032 public void testNoAvfDebugPolicyInLockedDevice() throws Exception { 1033 ITestDevice device = getDevice(); 1034 1035 // Check device's locked state with ro.boot.verifiedbootstate. ro.boot.flash.locked 1036 // may not be set if ro.oem_unlock_supported is false. 1037 String lockProp = device.getProperty("ro.boot.verifiedbootstate"); 1038 assumeFalse("Unlocked devices may have AVF debug policy", lockProp.equals("orange")); 1039 1040 // Test that AVF debug policy doesn't exist. 1041 boolean hasDebugPolicy = device.doesFileExist("/proc/device-tree/avf/guest"); 1042 assertThat(hasDebugPolicy).isFalse(); 1043 } 1044 1045 private boolean isLz4(String path) throws Exception { 1046 File lz4tool = findTestFile("lz4"); 1047 CommandResult result = 1048 createRunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path); 1049 return result.getStatus() == CommandStatus.SUCCESS; 1050 } 1051 1052 private void decompressLz4(String inputPath, String outputPath) throws Exception { 1053 File lz4tool = findTestFile("lz4"); 1054 CommandResult result = 1055 createRunUtil() 1056 .runTimedCmd( 1057 5000, lz4tool.getAbsolutePath(), "-d", "-f", inputPath, outputPath); 1058 String out = result.getStdout(); 1059 String err = result.getStderr(); 1060 assertWithMessage( 1061 "lz4 image " 1062 + inputPath 1063 + " decompression failed." 1064 + "\n\tout: " 1065 + out 1066 + "\n\terr: " 1067 + err 1068 + "\n") 1069 .about(command_results()) 1070 .that(result) 1071 .isSuccess(); 1072 } 1073 1074 private String avbInfo(String image_path) throws Exception { 1075 if (isLz4(image_path)) { 1076 File decompressedImage = FileUtil.createTempFile("decompressed", ".img"); 1077 decompressedImage.deleteOnExit(); 1078 decompressLz4(image_path, decompressedImage.getAbsolutePath()); 1079 image_path = decompressedImage.getAbsolutePath(); 1080 } 1081 1082 File avbtool = findTestFile("avbtool"); 1083 List<String> command = 1084 Arrays.asList(avbtool.getAbsolutePath(), "info_image", "--image", image_path); 1085 CommandResult result = 1086 createRunUtil().runTimedCmd(5000, "/bin/bash", "-c", String.join(" ", command)); 1087 String out = result.getStdout(); 1088 String err = result.getStderr(); 1089 assertWithMessage( 1090 "Command " 1091 + command 1092 + " failed." 1093 + ":\n\tout: " 1094 + out 1095 + "\n\terr: " 1096 + err 1097 + "\n") 1098 .about(command_results()) 1099 .that(result) 1100 .isSuccess(); 1101 return out; 1102 } 1103 1104 private void checkHashAlgorithm(File virtApexEtcDir) throws Exception { 1105 List<String> images = 1106 Arrays.asList( 1107 // kernel image (contains descriptors from initrd(s) as well) 1108 "/fs/microdroid_kernel", 1109 // vbmeta partition (contains descriptors from vendor/system images) 1110 "/fs/microdroid_vbmeta.img"); 1111 1112 for (String path : images) { 1113 String info = avbInfo(virtApexEtcDir + path); 1114 Pattern pattern = Pattern.compile("Hash Algorithm:[ ]*(sha1|sha256)"); 1115 Matcher m = pattern.matcher(info); 1116 while (m.find()) { 1117 assertThat(m.group(1)).isEqualTo("sha256"); 1118 } 1119 } 1120 } 1121 1122 @Test 1123 public void testDeviceAssignment() throws Exception { 1124 // Check for preconditions 1125 assumeVfioPlatformSupported(); 1126 1127 List<AssignableDevice> devices = getAssignableDevices(); 1128 assumeFalse("no assignable devices", devices.isEmpty()); 1129 1130 String dtSysfsPath = "/proc/device-tree/"; 1131 1132 // Try assign devices one by one 1133 for (AssignableDevice device : devices) { 1134 launchWithDeviceAssignment(device.node); 1135 1136 String dtPath = 1137 new CommandRunner(mMicrodroidDevice) 1138 .run("cat", dtSysfsPath + "__symbols__/" + device.dtbo_label); 1139 assertThat(dtPath).isNotEmpty(); 1140 1141 String resolvedDtPath = 1142 new CommandRunner(mMicrodroidDevice) 1143 .run("readlink", "-e", dtSysfsPath + dtPath); 1144 assertThat(resolvedDtPath).isNotEmpty(); 1145 1146 String allDevices = 1147 new CommandRunner(mMicrodroidDevice) 1148 .run("readlink", "-e", "/sys/bus/platform/devices/*/of_node"); 1149 assertThat(allDevices.split("\n")).asList().contains(resolvedDtPath); 1150 1151 getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice); 1152 mMicrodroidDevice = null; 1153 } 1154 } 1155 1156 private void launchWithDeviceAssignment(String device) throws Exception { 1157 Objects.requireNonNull(device); 1158 final String configPath = "assets/vm_config.json"; 1159 1160 mMicrodroidDevice = 1161 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1162 .debugLevel("full") 1163 .memoryMib(minMemorySize()) 1164 .cpuTopology("match_host") 1165 .protectedVm(mProtectedVm) 1166 .gki(mGki) 1167 .addAssignableDevice(device) 1168 .build(getAndroidDevice()); 1169 1170 assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue(); 1171 assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue(); 1172 } 1173 1174 @Test 1175 public void testGkiVersions() throws Exception { 1176 for (String gki : getSupportedGKIVersions()) { 1177 assertTrue( 1178 "Unknown gki \"" + gki + "\". Supported gkis: " + SUPPORTED_GKI_VERSIONS, 1179 SUPPORTED_GKI_VERSIONS.contains(gki)); 1180 } 1181 } 1182 1183 @Test 1184 public void testHugePages() throws Exception { 1185 ITestDevice device = getDevice(); 1186 boolean disableRoot = !device.isAdbRoot(); 1187 CommandRunner android = new CommandRunner(device); 1188 1189 final String SHMEM_ENABLED_PATH = "/sys/kernel/mm/transparent_hugepage/shmem_enabled"; 1190 String thpShmemStr = android.run("cat", SHMEM_ENABLED_PATH); 1191 1192 assumeFalse("shmem already enabled, skip", thpShmemStr.contains("[advise]")); 1193 assumeTrue("Unsupported shmem, skip", thpShmemStr.contains("[never]")); 1194 1195 device.enableAdbRoot(); 1196 assumeTrue("adb root is not enabled", device.isAdbRoot()); 1197 android.run("echo advise > " + SHMEM_ENABLED_PATH); 1198 1199 final String configPath = "assets/vm_config.json"; 1200 mMicrodroidDevice = 1201 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath) 1202 .debugLevel("full") 1203 .memoryMib(minMemorySize()) 1204 .cpuTopology("match_host") 1205 .protectedVm(mProtectedVm) 1206 .gki(mGki) 1207 .hugePages(true) 1208 .build(getAndroidDevice()); 1209 mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT); 1210 1211 android.run("echo never >" + SHMEM_ENABLED_PATH); 1212 if (disableRoot) { 1213 device.disableAdbRoot(); 1214 } 1215 } 1216 1217 @Before 1218 public void setUp() throws Exception { 1219 assumeDeviceIsCapable(getDevice()); 1220 mMetricPrefix = getMetricPrefix() + "microdroid/"; 1221 mMicrodroidDevice = null; 1222 1223 prepareVirtualizationTestSetup(getDevice()); 1224 1225 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ false); 1226 1227 // Skip test if given device doesn't support protected or non-protected VM. 1228 assumeTrue( 1229 "Microdroid is not supported for specific VM protection type", 1230 getAndroidDevice().supportsMicrodroid(mProtectedVm)); 1231 1232 if (mGki != null) { 1233 assumeTrue( 1234 "GKI version \"" + mGki + "\" is not supported on this device", 1235 getSupportedGKIVersions().contains(mGki)); 1236 } 1237 1238 mOs = (mGki != null) ? "microdroid_gki-" + mGki : "microdroid"; 1239 1240 new CommandRunner(getDevice()) 1241 .tryRun( 1242 "pm", 1243 "grant", 1244 SHELL_PACKAGE_NAME, 1245 "android.permission.USE_CUSTOM_VIRTUAL_MACHINE"); 1246 } 1247 1248 @After 1249 public void shutdown() throws Exception { 1250 if (mMicrodroidDevice != null) { 1251 getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice); 1252 } 1253 1254 cleanUpVirtualizationTestSetup(getDevice()); 1255 1256 archiveLogThenDelete( 1257 mTestLogs, getDevice(), LOG_PATH, "vm.log-" + mTestName.getMethodName()); 1258 1259 getDevice().uninstallPackage(PACKAGE_NAME); 1260 } 1261 1262 private void assumeProtectedVm() { 1263 assumeTrue("This test is only for protected VM.", mProtectedVm); 1264 } 1265 1266 private void assumeNonProtectedVm() { 1267 assumeFalse("This test is only for non-protected VM.", mProtectedVm); 1268 } 1269 1270 private void assumeVfioPlatformSupported() throws Exception { 1271 TestDevice device = getAndroidDevice(); 1272 assumeTrue( 1273 "Test skipped because VFIO platform is not supported.", 1274 device.doesFileExist("/dev/vfio/vfio") 1275 && device.doesFileExist("/sys/bus/platform/drivers/vfio-platform")); 1276 } 1277 1278 private void assumeUpdatableVmSupported() throws DeviceNotAvailableException { 1279 assumeTrue( 1280 "This test is only applicable if if Updatable VMs are supported", 1281 isUpdatableVmSupported()); 1282 } 1283 1284 private TestDevice getAndroidDevice() { 1285 TestDevice androidDevice = (TestDevice) getDevice(); 1286 assertThat(androidDevice).isNotNull(); 1287 return androidDevice; 1288 } 1289 1290 // The TradeFed Dockerfile sets LD_LIBRARY_PATH to a directory with an older libc++.so, which 1291 // breaks binaries that are linked against a newer libc++.so. Binaries commonly use DT_RUNPATH 1292 // to find an adjacent libc++.so (e.g. `$ORIGIN/../lib64`), but LD_LIBRARY_PATH overrides 1293 // DT_RUNPATH, so clear LD_LIBRARY_PATH. See b/332593805 and b/333782216. 1294 private static RunUtil createRunUtil() { 1295 RunUtil runUtil = new RunUtil(); 1296 runUtil.unsetEnvVariable("LD_LIBRARY_PATH"); 1297 return runUtil; 1298 } 1299 } 1300