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