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 android.virt.test; 18 19 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 20 21 import static org.hamcrest.CoreMatchers.containsString; 22 import static org.hamcrest.CoreMatchers.is; 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotEquals; 26 import static org.junit.Assert.assertThat; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.junit.Assume.assumeTrue; 30 31 import static java.util.stream.Collectors.toList; 32 33 import com.android.tradefed.device.DeviceNotAvailableException; 34 import com.android.tradefed.result.TestDescription; 35 import com.android.tradefed.result.TestResult; 36 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 37 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 38 import com.android.tradefed.util.CommandResult; 39 import com.android.tradefed.util.CommandStatus; 40 import com.android.tradefed.util.FileUtil; 41 import com.android.tradefed.util.RunUtil; 42 import com.android.tradefed.util.xml.AbstractXmlParser; 43 44 import org.json.JSONArray; 45 import org.json.JSONObject; 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Rule; 49 import org.junit.Test; 50 import org.junit.rules.TestName; 51 import org.junit.runner.RunWith; 52 import org.xml.sax.Attributes; 53 import org.xml.sax.helpers.DefaultHandler; 54 55 import java.io.ByteArrayInputStream; 56 import java.io.File; 57 import java.util.ArrayList; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Optional; 61 import java.util.concurrent.Callable; 62 import java.util.regex.Matcher; 63 import java.util.regex.Pattern; 64 65 @RunWith(DeviceJUnit4ClassRunner.class) 66 public class MicrodroidTestCase extends VirtualizationTestCaseBase { 67 private static final String APK_NAME = "MicrodroidTestApp.apk"; 68 private static final String PACKAGE_NAME = "com.android.microdroid.test"; 69 70 private static final int MIN_MEM_ARM64 = 145; 71 private static final int MIN_MEM_X86_64 = 196; 72 73 // Number of vCPUs and their affinity to host CPUs for testing purpose 74 private static final int NUM_VCPUS = 3; 75 private static final String CPU_AFFINITY = "0,1,2"; 76 77 @Rule public TestLogData mTestLogs = new TestLogData(); 78 @Rule public TestName mTestName = new TestName(); 79 minMemorySize()80 private int minMemorySize() throws DeviceNotAvailableException { 81 CommandRunner android = new CommandRunner(getDevice()); 82 String abi = android.run("getprop", "ro.product.cpu.abi"); 83 assertTrue(abi != null && !abi.isEmpty()); 84 if (abi.startsWith("arm64")) { 85 return MIN_MEM_ARM64; 86 } else if (abi.startsWith("x86_64")) { 87 return MIN_MEM_X86_64; 88 } 89 fail("Unsupported ABI: " + abi); 90 return 0; 91 } 92 isProtectedVmSupported()93 private boolean isProtectedVmSupported() throws DeviceNotAvailableException { 94 return getDevice().getBooleanProperty("ro.boot.hypervisor.protected_vm.supported", 95 false); 96 } 97 98 // Wait until logd-init starts. The service is one of the last services that are started in 99 // the microdroid boot procedure. Therefore, waiting for the service means that we wait for 100 // the boot to complete. TODO: we need a better marker eventually. waitForLogdInit()101 private void waitForLogdInit() { 102 tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\""); 103 } 104 105 @Test testCreateVmRequiresPermission()106 public void testCreateVmRequiresPermission() throws Exception { 107 // Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app 108 CommandRunner android = new CommandRunner(getDevice()); 109 android.run("pm", "revoke", PACKAGE_NAME, "android.permission.MANAGE_VIRTUAL_MACHINE"); 110 111 // Run MicrodroidTests#connectToVmService test, which should fail 112 final DeviceTestRunOptions options = new DeviceTestRunOptions(PACKAGE_NAME) 113 .setTestClassName(PACKAGE_NAME + ".MicrodroidTests") 114 .setTestMethodName("connectToVmService[protectedVm=false]") 115 .setCheckResults(false); 116 assertFalse(runDeviceTests(options)); 117 118 Map<TestDescription, TestResult> results = getLastDeviceRunResults().getTestResults(); 119 assertThat(results.size(), is(1)); 120 TestResult result = results.values().toArray(new TestResult[0])[0]; 121 assertTrue("The test should fail with a permission error", 122 result.getStackTrace() 123 .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission")); 124 } 125 newPartition(String label, String path)126 private static JSONObject newPartition(String label, String path) { 127 return new JSONObject(Map.of("label", label, "path", path)); 128 } 129 createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata)130 private void createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata) 131 throws Exception { 132 // mk_payload's config 133 File configFile = new File(payloadMetadata.getParentFile(), "payload_config.json"); 134 JSONObject config = new JSONObject(); 135 config.put("apk", 136 new JSONObject(Map.of("name", "microdroid-apk", "path", "", "idsig_path", ""))); 137 config.put("payload_config_path", "/mnt/apk/assets/vm_config.json"); 138 config.put("apexes", 139 new JSONArray( 140 apexes.stream() 141 .map(apex -> new JSONObject(Map.of("name", apex.name, "path", ""))) 142 .collect(toList()))); 143 FileUtil.writeToFile(config.toString(), configFile); 144 145 File mkPayload = findTestFile("mk_payload"); 146 RunUtil runUtil = new RunUtil(); 147 // Set the parent dir on the PATH (e.g. <workdir>/bin) 148 String separator = System.getProperty("path.separator"); 149 String path = mkPayload.getParentFile().getPath() + separator + System.getenv("PATH"); 150 runUtil.setEnvVariable("PATH", path); 151 152 List<String> command = new ArrayList<String>(); 153 command.add("mk_payload"); 154 command.add("--metadata-only"); 155 command.add(configFile.toString()); 156 command.add(payloadMetadata.toString()); 157 158 CommandResult result = runUtil.runTimedCmd( 159 // mk_payload should run fast enough 160 5 * 1000, 161 "/bin/bash", 162 "-c", 163 String.join(" ", command)); 164 String out = result.getStdout(); 165 String err = result.getStderr(); 166 assertEquals( 167 "creating payload metadata failed:\n\tout: " + out + "\n\terr: " + err + "\n", 168 CommandStatus.SUCCESS, result.getStatus()); 169 } 170 resignVirtApex(File virtApexDir, File signingKey, Map<String, File> keyOverrides)171 private void resignVirtApex(File virtApexDir, File signingKey, Map<String, File> keyOverrides) { 172 File signVirtApex = findTestFile("sign_virt_apex"); 173 174 RunUtil runUtil = new RunUtil(); 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<String>(); 181 command.add("sign_virt_apex"); 182 for (Map.Entry<String, File> entry : keyOverrides.entrySet()) { 183 String filename = entry.getKey(); 184 File overridingKey = entry.getValue(); 185 command.add("--key_override " + filename + "=" + overridingKey.getPath()); 186 } 187 command.add(signingKey.getPath()); 188 command.add(virtApexDir.getPath()); 189 190 CommandResult result = runUtil.runTimedCmd( 191 // sign_virt_apex is so slow on CI server that this often times 192 // out. Until we can make it fast, use 50s for timeout 193 50 * 1000, 194 "/bin/bash", 195 "-c", 196 String.join(" ", command)); 197 String out = result.getStdout(); 198 String err = result.getStderr(); 199 assertEquals( 200 "resigning the Virt APEX failed:\n\tout: " + out + "\n\terr: " + err + "\n", 201 CommandStatus.SUCCESS, result.getStatus()); 202 } 203 assertThatEventually(long timeoutMillis, Callable<T> callable, org.hamcrest.Matcher<T> matcher)204 private static <T> void assertThatEventually(long timeoutMillis, Callable<T> callable, 205 org.hamcrest.Matcher<T> matcher) throws Exception { 206 long start = System.currentTimeMillis(); 207 while (true) { 208 try { 209 assertThat(callable.call(), matcher); 210 return; 211 } catch (Throwable e) { 212 if (System.currentTimeMillis() - start < timeoutMillis) { 213 Thread.sleep(500); 214 } else { 215 throw e; 216 } 217 } 218 } 219 } 220 221 static class ActiveApexInfo { 222 public String name; 223 public String path; 224 public boolean provideSharedApexLibs; ActiveApexInfo(String name, String path, boolean provideSharedApexLibs)225 ActiveApexInfo(String name, String path, boolean provideSharedApexLibs) { 226 this.name = name; 227 this.path = path; 228 this.provideSharedApexLibs = provideSharedApexLibs; 229 } 230 } 231 232 static class ActiveApexInfoList { 233 private List<ActiveApexInfo> mList; ActiveApexInfoList(List<ActiveApexInfo> list)234 ActiveApexInfoList(List<ActiveApexInfo> list) { 235 this.mList = list; 236 } get(String apexName)237 ActiveApexInfo get(String apexName) { 238 for (ActiveApexInfo info: mList) { 239 if (info.name.equals(apexName)) { 240 return info; 241 } 242 } 243 return null; 244 } getSharedLibApexes()245 List<ActiveApexInfo> getSharedLibApexes() { 246 return mList.stream().filter(info -> info.provideSharedApexLibs).collect(toList()); 247 } 248 } 249 getActiveApexInfoList()250 private ActiveApexInfoList getActiveApexInfoList() throws Exception { 251 String apexInfoListXml = getDevice().pullFileContents("/apex/apex-info-list.xml"); 252 List<ActiveApexInfo> list = new ArrayList<>(); 253 new AbstractXmlParser() { 254 @Override 255 protected DefaultHandler createXmlHandler() { 256 return new DefaultHandler() { 257 @Override 258 public void startElement(String uri, String localName, String qName, 259 Attributes attributes) { 260 if (localName.equals("apex-info") 261 && attributes.getValue("isActive").equals("true")) { 262 String name = attributes.getValue("moduleName"); 263 String path = attributes.getValue("modulePath"); 264 String sharedApex = attributes.getValue("provideSharedApexLibs"); 265 list.add(new ActiveApexInfo(name, path, "true".equals(sharedApex))); 266 } 267 } 268 }; 269 } 270 }.parse(new ByteArrayInputStream(apexInfoListXml.getBytes())); 271 return new ActiveApexInfoList(list); 272 } 273 274 private String runMicrodroidWithResignedImages(File key, Map<String, File> keyOverrides, 275 boolean isProtected, boolean daemonize, String consolePath) 276 throws Exception { 277 CommandRunner android = new CommandRunner(getDevice()); 278 279 File virtApexDir = FileUtil.createTempDir("virt_apex"); 280 281 // Pull the virt apex's etc/ directory (which contains images and microdroid.json) 282 File virtApexEtcDir = new File(virtApexDir, "etc"); 283 // We need only etc/ directory for images 284 assertTrue(virtApexEtcDir.mkdirs()); 285 assertTrue(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)); 286 287 resignVirtApex(virtApexDir, key, keyOverrides); 288 289 // Push back re-signed virt APEX contents and updated microdroid.json 290 getDevice().pushDir(virtApexDir, TEST_ROOT); 291 292 // Create the idsig file for the APK 293 final String apkPath = getPathForPackage(PACKAGE_NAME); 294 final String idSigPath = TEST_ROOT + "idsig"; 295 android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath); 296 297 // Create the instance image for the VM 298 final String instanceImgPath = TEST_ROOT + "instance.img"; 299 android.run(VIRT_APEX + "bin/vm", "create-partition", "--type instance", 300 instanceImgPath, Integer.toString(10 * 1024 * 1024)); 301 302 // payload-metadata is created on device 303 final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img"; 304 305 // Load /apex/apex-info-list.xml to get paths to APEXes required for the VM. 306 ActiveApexInfoList list = getActiveApexInfoList(); 307 308 // Since Java APP can't start a VM with a custom image, here, we start a VM using `vm run` 309 // command with a VM Raw config which is equiv. to what virtualizationservice creates with 310 // a VM App config. 311 // 312 // 1. use etc/microdroid.json as base 313 // 2. add partitions: bootconfig, vbmeta, instance image 314 // 3. add a payload image disk with 315 // - payload-metadata 316 // - apexes 317 // - test apk 318 // - its idsig 319 320 // Load etc/microdroid.json 321 File microdroidConfigFile = new File(virtApexEtcDir, "microdroid.json"); 322 JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile)); 323 324 // Replace paths so that the config uses re-signed images from TEST_ROOT 325 config.put("bootloader", config.getString("bootloader").replace(VIRT_APEX, TEST_ROOT)); 326 JSONArray disks = config.getJSONArray("disks"); 327 for (int diskIndex = 0; diskIndex < disks.length(); diskIndex++) { 328 JSONObject disk = disks.getJSONObject(diskIndex); 329 JSONArray partitions = disk.getJSONArray("partitions"); 330 for (int partIndex = 0; partIndex < partitions.length(); partIndex++) { 331 JSONObject part = partitions.getJSONObject(partIndex); 332 part.put("path", part.getString("path").replace(VIRT_APEX, TEST_ROOT)); 333 } 334 } 335 336 // Add partitions to the second disk 337 final String vbmetaPath = TEST_ROOT + "etc/fs/microdroid_vbmeta_bootconfig.img"; 338 final String bootconfigPath = TEST_ROOT + "etc/microdroid_bootconfig.full_debuggable"; 339 disks.getJSONObject(1).getJSONArray("partitions") 340 .put(newPartition("vbmeta", vbmetaPath)) 341 .put(newPartition("bootconfig", bootconfigPath)) 342 .put(newPartition("vm-instance", instanceImgPath)); 343 344 // Add payload image disk with partitions: 345 // - payload-metadata 346 // - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional) 347 // - apk and idsig 348 List<ActiveApexInfo> apexesForVm = new ArrayList<>(); 349 apexesForVm.add(list.get("com.android.os.statsd")); 350 apexesForVm.add(list.get("com.android.adbd")); 351 apexesForVm.addAll(list.getSharedLibApexes()); 352 353 final JSONArray partitions = new JSONArray(); 354 partitions.put(newPartition("payload-metadata", payloadMetadataPath)); 355 int apexIndex = 0; 356 for (ActiveApexInfo apex : apexesForVm) { 357 partitions.put( 358 newPartition(String.format("microdroid-apex-%d", apexIndex++), apex.path)); 359 } 360 partitions 361 .put(newPartition("microdroid-apk", apkPath)) 362 .put(newPartition("microdroid-apk-idsig", idSigPath)); 363 disks.put(new JSONObject().put("writable", false).put("partitions", partitions)); 364 365 final File localPayloadMetadata = new File(virtApexDir, "payload-metadata.img"); 366 createPayloadMetadata(apexesForVm, localPayloadMetadata); 367 getDevice().pushFile(localPayloadMetadata, payloadMetadataPath); 368 369 config.put("protected", isProtected); 370 371 // Write updated raw config 372 final String configPath = TEST_ROOT + "raw_config.json"; 373 getDevice().pushString(config.toString(), configPath); 374 375 final String logPath = LOG_PATH; 376 final String ret = android.runWithTimeout( 377 60 * 1000, 378 VIRT_APEX + "bin/vm run", 379 daemonize ? "--daemonize" : "", 380 (consolePath != null) ? "--console " + consolePath : "", 381 "--log " + logPath, 382 configPath); 383 Pattern pattern = Pattern.compile("with CID (\\d+)"); 384 Matcher matcher = pattern.matcher(ret); 385 assertTrue(matcher.find()); 386 return matcher.group(1); 387 } 388 389 @Test 390 public void testBootFailsWhenProtectedVmStartsWithImagesSignedWithDifferentKey() 391 throws Exception { 392 assumeTrue(isProtectedVmSupported()); 393 394 File key = findTestFile("test.com.android.virt.pem"); 395 Map<String, File> keyOverrides = Map.of(); 396 boolean isProtected = true; 397 boolean daemonize = false; // VM should shut down due to boot failure. 398 String consolePath = TEST_ROOT + "console"; 399 runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, consolePath); 400 assertThat(getDevice().pullFileContents(consolePath), 401 containsString("pvmfw boot failed")); 402 } 403 404 @Test 405 public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey() 406 throws Exception { 407 File key = findTestFile("test.com.android.virt.pem"); 408 Map<String, File> keyOverrides = Map.of(); 409 boolean isProtected = false; 410 boolean daemonize = true; 411 String consolePath = TEST_ROOT + "console"; 412 String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, 413 consolePath); 414 // Adb connection to the microdroid means that boot succeeded. 415 adbConnectToMicrodroid(getDevice(), cid); 416 shutdownMicrodroid(getDevice(), cid); 417 } 418 419 @Test 420 public void testBootFailsWhenBootloaderAndVbMetaAreSignedWithDifferentKeys() 421 throws Exception { 422 // Sign everything with key1 except vbmeta 423 File key = findTestFile("test.com.android.virt.pem"); 424 File key2 = findTestFile("test2.com.android.virt.pem"); 425 Map<String, File> keyOverrides = Map.of( 426 "microdroid_vbmeta.img", key2); 427 boolean isProtected = false; // Not interested in pvwfw 428 boolean daemonize = true; // Bootloader fails and enters prompts. 429 // To be able to stop it, it should be a daemon. 430 String consolePath = TEST_ROOT + "console"; 431 String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, 432 consolePath); 433 // Wail for a while so that bootloader prints errors to console 434 assertThatEventually(10000, () -> getDevice().pullFileContents(consolePath), 435 containsString("Public key was rejected")); 436 shutdownMicrodroid(getDevice(), cid); 437 } 438 439 @Test 440 public void testBootSucceedsWhenBootloaderAndVbmetaHaveSameSigningKeys() 441 throws Exception { 442 // Sign everything with key1 except bootloader and vbmeta 443 File key = findTestFile("test.com.android.virt.pem"); 444 File key2 = findTestFile("test2.com.android.virt.pem"); 445 Map<String, File> keyOverrides = Map.of( 446 "microdroid_bootloader", key2, 447 "microdroid_vbmeta.img", key2, 448 "microdroid_vbmeta_bootconfig.img", key2); 449 boolean isProtected = false; // Not interested in pvwfw 450 boolean daemonize = true; // Bootloader should succeed. 451 // To be able to stop it, it should be a daemon. 452 String consolePath = TEST_ROOT + "console"; 453 String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, 454 consolePath); 455 // Adb connection to the microdroid means that boot succeeded. 456 adbConnectToMicrodroid(getDevice(), cid); 457 shutdownMicrodroid(getDevice(), cid); 458 } 459 460 @Test 461 public void testTombstonesAreBeingForwarded() throws Exception { 462 // This test requires rooting. Skip on user builds where rooting is impossible. 463 final String buildType = getDevice().getProperty("ro.build.type"); 464 assumeTrue("userdebug".equals(buildType) || "eng".equals(buildType)); 465 466 // Note this test relies on logcat values being printed by tombstone_transmit on 467 // and the reeceiver on host (virtualization_service) 468 final String configPath = "assets/vm_config.json"; // path inside the APK 469 final String cid = 470 startMicrodroid( 471 getDevice(), 472 getBuild(), 473 APK_NAME, 474 PACKAGE_NAME, 475 configPath, 476 /* debug */ true, 477 minMemorySize(), 478 Optional.of(NUM_VCPUS), 479 Optional.of(CPU_AFFINITY)); 480 adbConnectToMicrodroid(getDevice(), cid); 481 waitForLogdInit(); 482 runOnMicrodroid("logcat -c"); 483 // We need root permission to write to /data/tombstones/ 484 rootMicrodroid(); 485 // Write a test tombstone file in /data/tombstones 486 runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'" 487 + "> /data/tombstones/transmit.txt"); 488 // check if the tombstone have been tranferred from VM 489 assertNotEquals(runOnMicrodroid("timeout 15s logcat | grep -m 1 " 490 + "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'"), 491 ""); 492 // Confirm that tombstone is received (from host logcat) 493 assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(), 494 "logcat", "-d", "-e", 495 "Received 34 bytes from guest & wrote to tombstone file.*"), 496 ""); 497 } 498 499 @Test 500 public void testMicrodroidBoots() throws Exception { 501 final String configPath = "assets/vm_config.json"; // path inside the APK 502 final String cid = 503 startMicrodroid( 504 getDevice(), 505 getBuild(), 506 APK_NAME, 507 PACKAGE_NAME, 508 configPath, 509 /* debug */ true, 510 minMemorySize(), 511 Optional.of(NUM_VCPUS), 512 Optional.of(CPU_AFFINITY)); 513 adbConnectToMicrodroid(getDevice(), cid); 514 waitForLogdInit(); 515 // Test writing to /data partition 516 runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt"); 517 assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest")); 518 519 // Check if the APK & its idsig partitions exist 520 final String apkPartition = "/dev/block/by-name/microdroid-apk"; 521 assertThat(runOnMicrodroid("ls", apkPartition), is(apkPartition)); 522 final String apkIdsigPartition = "/dev/block/by-name/microdroid-apk-idsig"; 523 assertThat(runOnMicrodroid("ls", apkIdsigPartition), is(apkIdsigPartition)); 524 // Check the vm-instance partition as well 525 final String vmInstancePartition = "/dev/block/by-name/vm-instance"; 526 assertThat(runOnMicrodroid("ls", vmInstancePartition), is(vmInstancePartition)); 527 528 // Check if the native library in the APK is has correct filesystem info 529 final String[] abis = runOnMicrodroid("getprop", "ro.product.cpu.abilist").split(","); 530 assertThat(abis.length, is(1)); 531 final String testLib = "/mnt/apk/lib/" + abis[0] + "/MicrodroidTestNativeLib.so"; 532 final String label = "u:object_r:system_file:s0"; 533 assertThat(runOnMicrodroid("ls", "-Z", testLib), is(label + " " + testLib)); 534 535 // Check that no denials have happened so far 536 assertThat(runOnMicrodroid("logcat -d -e 'avc:[[:space:]]{1,2}denied'"), is("")); 537 538 assertThat(runOnMicrodroid("cat /proc/cpuinfo | grep processor | wc -l"), 539 is(Integer.toString(NUM_VCPUS))); 540 541 // Check that selinux is enabled 542 assertThat(runOnMicrodroid("getenforce"), is("Enforcing")); 543 544 // TODO(b/176805428): adb is broken for nested VM 545 if (!isCuttlefish()) { 546 // Check neverallow rules on microdroid 547 File policyFile = FileUtil.createTempFile("microdroid_sepolicy", ""); 548 pullMicrodroidFile("/sys/fs/selinux/policy", policyFile); 549 550 File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf"); 551 File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze"); 552 553 CommandResult result = 554 RunUtil.getDefault() 555 .runTimedCmd( 556 10000, 557 sepolicyAnalyzeBin.getPath(), 558 policyFile.getPath(), 559 "neverallow", 560 "-w", 561 "-f", 562 generalPolicyConfFile.getPath()); 563 assertEquals( 564 "neverallow check failed: " + result.getStderr().trim(), 565 result.getStatus(), 566 CommandStatus.SUCCESS); 567 } 568 569 shutdownMicrodroid(getDevice(), cid); 570 } 571 572 @Before 573 public void setUp() throws Exception { 574 testIfDeviceIsCapable(getDevice()); 575 576 prepareVirtualizationTestSetup(getDevice()); 577 578 getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false); 579 580 // clear the log 581 getDevice().executeShellV2Command("logcat -c"); 582 } 583 584 @After 585 public void shutdown() throws Exception { 586 cleanUpVirtualizationTestSetup(getDevice()); 587 588 archiveLogThenDelete(mTestLogs, getDevice(), LOG_PATH, 589 "vm.log-" + mTestName.getMethodName()); 590 591 getDevice().uninstallPackage(PACKAGE_NAME); 592 } 593 } 594