• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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.tradefed.device.TestDevice.MicrodroidBuilder;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import static org.junit.Assume.assumeTrue;
25 import static org.junit.Assume.assumeFalse;
26 import static org.junit.Assert.assertThrows;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 
31 import com.android.microdroid.test.host.CommandRunner;
32 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
33 import com.android.microdroid.test.host.Pvmfw;
34 import com.android.tradefed.device.DeviceNotAvailableException;
35 import com.android.tradefed.device.DeviceRuntimeException;
36 import com.android.tradefed.device.ITestDevice;
37 import com.android.tradefed.device.TestDevice;
38 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
39 import com.android.tradefed.util.CommandStatus;
40 import com.android.tradefed.util.CommandResult;
41 import com.android.tradefed.util.FileUtil;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 
48 import java.io.File;
49 import java.util.Objects;
50 import java.util.concurrent.TimeUnit;
51 
52 /** Tests debug policy */
53 @RunWith(DeviceJUnit4ClassRunner.class)
54 public class DebugPolicyHostTests extends MicrodroidHostTestCaseBase {
55     @NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
56     @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
57     @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
58     @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
59     @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
60     @NonNull private static final String MICRODROID_DEBUG_NONE = "none";
61     @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
62     @NonNull private static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
63     private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
64     private static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
65     private static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
66 
67     @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
68     @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
69     @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
70     @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
71 
72     @NonNull private static final String CUSTOM_DEBUG_POLICY_FILE_NAME = "debug_policy.dtb";
73 
74     @NonNull
75     private static final String CUSTOM_DEBUG_POLICY_PATH =
76             TEST_ROOT + CUSTOM_DEBUG_POLICY_FILE_NAME;
77 
78     @NonNull
79     private static final String CUSTOM_DEBUG_POLICY_PATH_PROP =
80             "hypervisor.virtualizationmanager.debug_policy.path";
81 
82     @NonNull
83     private static final String AVF_DEBUG_POLICY_ADB_DT_PROP_PATH = "/avf/guest/microdroid/adb";
84 
85     @NonNull private static final String MICRODROID_CMDLINE_PATH = "/proc/cmdline";
86     @NonNull private static final String MICRODROID_DT_ROOT_PATH = "/proc/device-tree";
87 
88     @NonNull
89     private static final String MICRODROID_DT_BOOTARGS_PATH =
90             MICRODROID_DT_ROOT_PATH + "/chosen/bootargs";
91 
92     @NonNull
93     private static final String MICRODROID_DT_RAMDUMP_PATH =
94             MICRODROID_DT_ROOT_PATH + "/avf/guest/common/ramdump";
95 
96     @NonNull private static final String HEX_STRING_ZERO = "00000000";
97     @NonNull private static final String HEX_STRING_ONE = "00000001";
98 
99     @Nullable private static File mPvmfwBinFileOnHost;
100     @Nullable private static File mBccFileOnHost;
101 
102     @Nullable private TestDevice mAndroidDevice;
103     @Nullable private ITestDevice mMicrodroidDevice;
104     @Nullable private File mCustomPvmfwBinFileOnHost;
105     @Nullable private File mCustomDebugPolicyFileOnHost;
106 
107     @Before
setUp()108     public void setUp() throws Exception {
109         mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
110 
111         // Check device capabilities
112         assumeDeviceIsCapable(mAndroidDevice);
113         assumeTrue(
114                 "Skip if protected VMs are not supported",
115                 mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
116         assumeFalse("Test requires setprop for using custom pvmfw and adb root", isUserBuild());
117 
118         mAndroidDevice.enableAdbRoot();
119 
120         // tradefed copies the test artfacts under /tmp when running tests,
121         // so we should *find* the artifacts with the file name.
122         mPvmfwBinFileOnHost =
123                 getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
124         mBccFileOnHost =
125                 getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
126 
127         // Prepare for system properties for custom debug policy.
128         // File will be prepared later in individual test by setupCustomDebugPolicy()
129         // and then pushed to device when launching with launchProtectedVmAndWaitForBootCompleted()
130         // or tryLaunchProtectedNonDebuggableVm().
131         mCustomPvmfwBinFileOnHost =
132                 FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
133         mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
134         mAndroidDevice.setProperty(CUSTOM_DEBUG_POLICY_PATH_PROP, CUSTOM_DEBUG_POLICY_PATH);
135 
136         // Prepare for launching microdroid
137         mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
138         prepareVirtualizationTestSetup(mAndroidDevice);
139         mMicrodroidDevice = null;
140     }
141 
142     @After
shutdown()143     public void shutdown() throws Exception {
144         if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
145             return;
146         }
147         if (mMicrodroidDevice != null) {
148             mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
149             mMicrodroidDevice = null;
150         }
151         mAndroidDevice.uninstallPackage(PACKAGE_NAME);
152 
153         // Cleanup for custom debug policies
154         mAndroidDevice.setProperty(CUSTOM_DEBUG_POLICY_PATH_PROP, "");
155         mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, "");
156         FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
157 
158         cleanUpVirtualizationTestSetup(mAndroidDevice);
159 
160         mAndroidDevice.disableAdbRoot();
161     }
162 
163     @Test
testAdbInDebugPolicy_withDebugLevelNone_bootWithAdbConnection()164     public void testAdbInDebugPolicy_withDebugLevelNone_bootWithAdbConnection() throws Exception {
165         prepareCustomDebugPolicy("avf_debug_policy_with_adb.dtbo");
166 
167         launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
168     }
169 
170     @Test
testNoAdbInDebugPolicy_withDebugLevelNone_boots()171     public void testNoAdbInDebugPolicy_withDebugLevelNone_boots() throws Exception {
172         prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
173 
174         // VM would boot, but cannot verify directly because of no adbd in the VM.
175         CommandResult result = tryLaunchProtectedNonDebuggableVm();
176         assertThat(result.getStatus()).isEqualTo(CommandStatus.TIMED_OUT);
177         assertWithMessage("Microdroid should have booted")
178                 .that(result.getStderr())
179                 .contains("payload is ready");
180     }
181 
182     @Test
testNoAdbInDebugPolicy_withDebugLevelNone_noConnection()183     public void testNoAdbInDebugPolicy_withDebugLevelNone_noConnection() throws Exception {
184         prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
185 
186         assertThrows(
187                 "Microdroid shouldn't be recognized because of missing adb connection",
188                 DeviceRuntimeException.class,
189                 () ->
190                         launchProtectedVmAndWaitForBootCompleted(
191                                 MICRODROID_DEBUG_NONE, BOOT_FAILURE_WAIT_TIME_MS));
192     }
193 
194     @Test
testNoAdbInDebugPolicy_withDebugLevelFull_bootWithAdbConnection()195     public void testNoAdbInDebugPolicy_withDebugLevelFull_bootWithAdbConnection() throws Exception {
196         prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
197 
198         launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
199     }
200 
isDebugPolicyEnabled(@onNull String dtPropertyPath)201     private boolean isDebugPolicyEnabled(@NonNull String dtPropertyPath)
202             throws DeviceNotAvailableException {
203         CommandRunner runner = new CommandRunner(mAndroidDevice);
204         CommandResult result =
205                 runner.runForResult("xxd", "-p", "/proc/device-tree" + dtPropertyPath);
206         if (result.getStatus() == CommandStatus.SUCCESS) {
207             return HEX_STRING_ONE.equals(result.getStdout().trim());
208         }
209         return false;
210     }
211 
212     @NonNull
readMicrodroidFileAsString(@onNull String path)213     private String readMicrodroidFileAsString(@NonNull String path)
214             throws DeviceNotAvailableException {
215         return new CommandRunner(mMicrodroidDevice).run("cat", path);
216     }
217 
218     @NonNull
readMicrodroidFileAsHexString(@onNull String path)219     private String readMicrodroidFileAsHexString(@NonNull String path)
220             throws DeviceNotAvailableException {
221         return new CommandRunner(mMicrodroidDevice).run("xxd", "-p", path);
222     }
223 
prepareCustomDebugPolicy(@onNull String debugPolicyFileName)224     private void prepareCustomDebugPolicy(@NonNull String debugPolicyFileName) throws Exception {
225         mCustomDebugPolicyFileOnHost =
226                 getTestInformation()
227                         .getDependencyFile(debugPolicyFileName, /* targetFirst= */ false);
228 
229         Pvmfw pvmfw =
230                 new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost)
231                         .setDebugPolicyOverlay(mCustomDebugPolicyFileOnHost)
232                         .build();
233         pvmfw.serialize(mCustomPvmfwBinFileOnHost);
234     }
235 
hasConsoleOutput(@onNull CommandResult result)236     private boolean hasConsoleOutput(@NonNull CommandResult result)
237             throws DeviceNotAvailableException {
238         return result.getStdout().contains("Run /init as init process");
239     }
240 
hasMicrodroidLogcatOutput()241     private boolean hasMicrodroidLogcatOutput() throws DeviceNotAvailableException {
242         CommandResult result =
243                 new CommandRunner(mAndroidDevice).runForResult("test", "-s", MICRODROID_LOG_PATH);
244         return result.getExitCode() == 0;
245     }
246 
launchProtectedVmAndWaitForBootCompleted(String debugLevel)247     private ITestDevice launchProtectedVmAndWaitForBootCompleted(String debugLevel)
248             throws DeviceNotAvailableException {
249         return launchProtectedVmAndWaitForBootCompleted(debugLevel, BOOT_COMPLETE_TIMEOUT_MS);
250     }
251 
launchProtectedVmAndWaitForBootCompleted( String debugLevel, long adbTimeoutMs)252     private ITestDevice launchProtectedVmAndWaitForBootCompleted(
253             String debugLevel, long adbTimeoutMs) throws DeviceNotAvailableException {
254         mMicrodroidDevice =
255                 MicrodroidBuilder.fromDevicePath(
256                                 getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
257                         .debugLevel(debugLevel)
258                         .protectedVm(/* protectedVm= */ true)
259                         .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
260                         .addBootFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_FILE_NAME)
261                         .setAdbConnectTimeoutMs(adbTimeoutMs)
262                         .build(mAndroidDevice);
263         assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
264         assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
265         return mMicrodroidDevice;
266     }
267 
268     // Try to launch protected non-debuggable VM for a while and quit.
269     // Non-debuggable VM might not enable adb, so there's no ITestDevice instance of it.
tryLaunchProtectedNonDebuggableVm()270     private CommandResult tryLaunchProtectedNonDebuggableVm() throws DeviceNotAvailableException {
271         // Can't use MicrodroidBuilder because it expects adb connection
272         // but non-debuggable VM may not enable adb.
273         CommandRunner runner = new CommandRunner(mAndroidDevice);
274         runner.run("mkdir", "-p", TEST_ROOT);
275         mAndroidDevice.pushFile(mCustomPvmfwBinFileOnHost, CUSTOM_PVMFW_IMG_PATH);
276         mAndroidDevice.pushFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_PATH);
277 
278         // This will fail because app wouldn't finish itself.
279         // But let's run the app once and get logs.
280         String command =
281                 String.join(
282                         " ",
283                         "/apex/com.android.virt/bin/vm",
284                         "run-app",
285                         "--log",
286                         MICRODROID_LOG_PATH,
287                         "--protected",
288                         getPathForPackage(PACKAGE_NAME),
289                         TEST_ROOT + "idsig",
290                         TEST_ROOT + "instance.img",
291                         "--config-path",
292                         MICRODROID_CONFIG_PATH);
293         return mAndroidDevice.executeShellV2Command(
294                 command, CONSOLE_OUTPUT_WAIT_MS, TimeUnit.MILLISECONDS, /* retryAttempts= */ 0);
295     }
296 }
297