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