• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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