1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.cts; 18 19 import com.android.compatibility.common.util.MetricsReportLog; 20 import com.android.compatibility.common.util.ResultType; 21 import com.android.compatibility.common.util.ResultUnit; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.config.Option; 24 import com.android.tradefed.testtype.IBuildReceiver; 25 import com.android.tradefed.testtype.IAbi; 26 import com.android.tradefed.testtype.IAbiReceiver; 27 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.device.NativeDevice; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 33 import com.android.ddmlib.Log; 34 35 import org.junit.rules.TestName; 36 import org.junit.Rule; 37 import org.junit.After; 38 import org.junit.Before; 39 import org.junit.runner.RunWith; 40 41 import java.util.Map; 42 import java.util.HashMap; 43 import java.util.regex.Pattern; 44 import java.util.regex.Matcher; 45 import java.util.concurrent.Callable; 46 import java.math.BigInteger; 47 48 import static org.junit.Assert.*; 49 import static org.junit.Assume.*; 50 51 public class SecurityTestCase extends BaseHostJUnit4Test { 52 53 private static final String LOG_TAG = "SecurityTestCase"; 54 private static final int RADIX_HEX = 16; 55 56 protected static final int TIMEOUT_DEFAULT = 60; 57 // account for the poc timer of 5 minutes (+15 seconds for safety) 58 protected static final int TIMEOUT_NONDETERMINISTIC = 315; 59 60 private long kernelStartTime; 61 62 private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this); 63 64 @Rule public TestName testName = new TestName(); 65 @Rule public PocPusher pocPusher = new PocPusher(); 66 67 private static Map<ITestDevice, IBuildInfo> sBuildInfo = new HashMap<>(); 68 private static Map<ITestDevice, IAbi> sAbi = new HashMap<>(); 69 private static Map<ITestDevice, String> sTestName = new HashMap<>(); 70 private static Map<ITestDevice, PocPusher> sPocPusher = new HashMap<>(); 71 72 @Option(name = "set-kptr_restrict", 73 description = "If kptr_restrict should be set to 2 after every reboot") 74 private boolean setKptr_restrict = false; 75 private boolean ignoreKernelAddress = false; 76 77 /** 78 * Waits for device to be online, marks the most recent boottime of the device 79 */ 80 @Before setUp()81 public void setUp() throws Exception { 82 getDevice().waitForDeviceAvailable(); 83 getDevice().disableAdbRoot(); 84 updateKernelStartTime(); 85 // TODO:(badash@): Watch for other things to track. 86 // Specifically time when app framework starts 87 88 sBuildInfo.put(getDevice(), getBuild()); 89 sAbi.put(getDevice(), getAbi()); 90 sTestName.put(getDevice(), testName.getMethodName()); 91 92 pocPusher.setDevice(getDevice()).setBuild(getBuild()).setAbi(getAbi()); 93 sPocPusher.put(getDevice(), pocPusher); 94 95 if (setKptr_restrict) { 96 if (getDevice().enableAdbRoot()) { 97 CLog.i("setting kptr_restrict to 2"); 98 getDevice().executeShellCommand("echo 2 > /proc/sys/kernel/kptr_restrict"); 99 getDevice().disableAdbRoot(); 100 } else { 101 // not a rootable device 102 ignoreKernelAddress = true; 103 } 104 } 105 } 106 107 /** 108 * Makes sure the phone is online, and the ensure the current boottime is within 2 seconds 109 * (due to rounding) of the previous boottime to check if The phone has crashed. 110 */ 111 @After tearDown()112 public void tearDown() throws Exception { 113 try { 114 getDevice().waitForDeviceAvailable(90 * 1000); 115 } catch (DeviceNotAvailableException e) { 116 // Force a disconnection of all existing sessions to see if that unsticks adbd. 117 getDevice().executeAdbCommand("reconnect"); 118 getDevice().waitForDeviceAvailable(30 * 1000); 119 } 120 121 long deviceTime = getDeviceUptime() + kernelStartTime; 122 long hostTime = System.currentTimeMillis() / 1000; 123 assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); 124 125 // TODO(badash@): add ability to catch runtime restart 126 } 127 getBuildInfo(ITestDevice device)128 public static IBuildInfo getBuildInfo(ITestDevice device) { 129 return sBuildInfo.get(device); 130 } 131 getAbi(ITestDevice device)132 public static IAbi getAbi(ITestDevice device) { 133 return sAbi.get(device); 134 } 135 getTestName(ITestDevice device)136 public static String getTestName(ITestDevice device) { 137 return sTestName.get(device); 138 } 139 getPocPusher(ITestDevice device)140 public static PocPusher getPocPusher(ITestDevice device) { 141 return sPocPusher.get(device); 142 } 143 144 // TODO convert existing assertMatches*() to RegexUtils.assertMatches*() 145 // b/123237827 146 @Deprecated assertMatches(String pattern, String input)147 public void assertMatches(String pattern, String input) throws Exception { 148 RegexUtils.assertContains(pattern, input); 149 } 150 151 @Deprecated assertMatchesMultiLine(String pattern, String input)152 public void assertMatchesMultiLine(String pattern, String input) throws Exception { 153 RegexUtils.assertContainsMultiline(pattern, input); 154 } 155 156 @Deprecated assertNotMatches(String pattern, String input)157 public void assertNotMatches(String pattern, String input) throws Exception { 158 RegexUtils.assertNotContains(pattern, input); 159 } 160 161 @Deprecated assertNotMatchesMultiLine(String pattern, String input)162 public void assertNotMatchesMultiLine(String pattern, String input) throws Exception { 163 RegexUtils.assertNotContainsMultiline(pattern, input); 164 } 165 166 /** 167 * Runs a provided function that collects a String to test against kernel pointer leaks. 168 * The getPtrFunction function implementation must return a String that starts with the 169 * pointer. i.e. "01234567". Trailing characters are allowed except for [0-9a-fA-F]. In 170 * the event that the pointer appears to be vulnerable, a JUnit assert is thrown. Since kernel 171 * pointers can be hashed, there is a possiblity the the hashed pointer overlaps into the 172 * normal kernel space. The test re-runs to make false positives statistically insignificant. 173 * When kernel pointers won't change without a reboot, provide a device to reboot. 174 * 175 * @param getPtrFunction a function that returns a string that starts with a pointer 176 * @param deviceToReboot device to reboot when kernel pointers won't change 177 */ assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)178 public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot) 179 throws Exception { 180 assumeFalse("Cannot set kptr_restrict to 2, ignoring kptr test.", ignoreKernelAddress); 181 String ptr = null; 182 for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive 183 ptr = getPtrFunction.call(); 184 if (ptr == null) { 185 return; 186 } 187 if (!isKptr(ptr)) { 188 // quit early because the ptr is likely hashed or zeroed. 189 return; 190 } 191 if (deviceToReboot != null) { 192 deviceToReboot.nonBlockingReboot(); 193 deviceToReboot.waitForDeviceAvailable(); 194 updateKernelStartTime(); 195 } 196 } 197 fail("\"" + ptr + "\" is an exposed kernel pointer."); 198 } 199 isKptr(String ptr)200 private boolean isKptr(String ptr) { 201 Matcher m = Pattern.compile("[0-9a-fA-F]*").matcher(ptr); 202 if (!m.find() || m.start() != 0) { 203 // ptr string is malformed 204 return false; 205 } 206 int length = m.end(); 207 208 if (length == 8) { 209 // 32-bit pointer 210 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 211 // 32-bit kernel memory range: 0xC0000000 -> 0xffffffff 212 // 0x3fffffff bytes = 1GB / 0xffffffff = 4 GB 213 // 1 in 4 collision for hashed pointers 214 return address.compareTo(new BigInteger("C0000000", RADIX_HEX)) >= 0; 215 } else if (length == 16) { 216 // 64-bit pointer 217 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 218 // 64-bit kernel memory range: 0x8000000000000000 -> 0xffffffffffffffff 219 // 48-bit implementation: 0xffff800000000000; 1 in 131,072 collision 220 // 56-bit implementation: 0xff80000000000000; 1 in 512 collision 221 // 64-bit implementation: 0x8000000000000000; 1 in 2 collision 222 return address.compareTo(new BigInteger("ff80000000000000", RADIX_HEX)) >= 0; 223 } 224 225 return false; 226 } 227 228 /** 229 * Check if a driver is present on a machine. 230 */ containsDriver(ITestDevice device, String driver)231 protected boolean containsDriver(ITestDevice device, String driver) throws Exception { 232 boolean containsDriver = false; 233 if (driver.contains("*")) { 234 // -A list all files but . and .. 235 // -d directory, not contents 236 // -1 list one file per line 237 // -f unsorted 238 String ls = "ls -A -d -1 -f " + driver; 239 if (AdbUtils.runCommandGetExitCode(ls, device) == 0) { 240 String[] expanded = device.executeShellCommand(ls).split("\\R"); 241 for (String expandedDriver : expanded) { 242 containsDriver |= containsDriver(device, expandedDriver); 243 } 244 } 245 } else { 246 containsDriver = AdbUtils.runCommandGetExitCode("test -r " + driver, device) == 0; 247 } 248 249 MetricsReportLog reportLog = buildMetricsReportLog(getDevice()); 250 reportLog.addValue("path", driver, ResultType.NEUTRAL, ResultUnit.NONE); 251 reportLog.addValue("exists", containsDriver, ResultType.NEUTRAL, ResultUnit.NONE); 252 reportLog.submit(); 253 254 return containsDriver; 255 } 256 buildMetricsReportLog(ITestDevice device)257 protected static MetricsReportLog buildMetricsReportLog(ITestDevice device) { 258 IBuildInfo buildInfo = getBuildInfo(device); 259 IAbi abi = getAbi(device); 260 String testName = getTestName(device); 261 262 StackTraceElement[] stacktraces = Thread.currentThread().getStackTrace(); 263 int stackDepth = 2; // 0: getStackTrace(), 1: buildMetricsReportLog, 2: caller 264 String className = stacktraces[stackDepth].getClassName(); 265 String methodName = stacktraces[stackDepth].getMethodName(); 266 String classMethodName = String.format("%s#%s", className, methodName); 267 268 // The stream name must be snake_case or else json formatting breaks 269 String streamName = methodName.replaceAll("(\\p{Upper})", "_$1").toLowerCase(); 270 271 MetricsReportLog reportLog = new MetricsReportLog( 272 buildInfo, 273 abi.getName(), 274 classMethodName, 275 "CtsSecurityBulletinHostTestCases", 276 streamName, 277 true); 278 reportLog.addValue("test_name", testName, ResultType.NEUTRAL, ResultUnit.NONE); 279 return reportLog; 280 } 281 getDeviceUptime()282 private long getDeviceUptime() throws DeviceNotAvailableException { 283 String uptime = null; 284 int attempts = 5; 285 do { 286 if (attempts-- <= 0) { 287 throw new RuntimeException("could not get device uptime"); 288 } 289 getDevice().waitForDeviceAvailable(); 290 uptime = getDevice().executeShellCommand("cat /proc/uptime").trim(); 291 } while (uptime.isEmpty()); 292 return Long.parseLong(uptime.substring(0, uptime.indexOf('.'))); 293 } 294 safeReboot()295 public void safeReboot() throws DeviceNotAvailableException { 296 getDevice().nonBlockingReboot(); 297 getDevice().waitForDeviceAvailable(); 298 updateKernelStartTime(); 299 } 300 301 /** 302 * Allows a test to pass if called after a planned reboot. 303 */ updateKernelStartTime()304 public void updateKernelStartTime() throws DeviceNotAvailableException { 305 long uptime = getDeviceUptime(); 306 kernelStartTime = (System.currentTimeMillis() / 1000) - uptime; 307 } 308 309 /** 310 * Return true if a module is play managed. 311 * 312 * Example of skipping a test based on mainline modules: 313 * <pre> 314 * @Test 315 * public void testPocCVE_1234_5678() throws Exception { 316 * // This will skip the test if MODULE_METADATA mainline module is play managed. 317 * assumeFalse(moduleIsPlayManaged("com.google.android.captiveportallogin")); 318 * // Do testing... 319 * } 320 * * </pre> 321 */ moduleIsPlayManaged(String modulePackageName)322 boolean moduleIsPlayManaged(String modulePackageName) throws Exception { 323 return mainlineModuleDetector.getPlayManagedModules().contains(modulePackageName); 324 } 325 } 326