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