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 import static org.hamcrest.core.Is.is; 51 52 public class SecurityTestCase extends BaseHostJUnit4Test { 53 54 private static final String LOG_TAG = "SecurityTestCase"; 55 private static final int RADIX_HEX = 16; 56 57 protected static final int TIMEOUT_DEFAULT = 60; 58 // account for the poc timer of 5 minutes (+15 seconds for safety) 59 protected static final int TIMEOUT_NONDETERMINISTIC = 315; 60 61 private long kernelStartTime; 62 63 private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this); 64 65 @Rule public TestName testName = new TestName(); 66 @Rule public PocPusher pocPusher = new PocPusher(); 67 68 private static Map<ITestDevice, IBuildInfo> sBuildInfo = new HashMap<>(); 69 private static Map<ITestDevice, IAbi> sAbi = new HashMap<>(); 70 private static Map<ITestDevice, String> sTestName = new HashMap<>(); 71 private static Map<ITestDevice, PocPusher> sPocPusher = new HashMap<>(); 72 73 @Option(name = "set-kptr_restrict", 74 description = "If kptr_restrict should be set to 2 after every reboot") 75 private boolean setKptr_restrict = false; 76 private boolean ignoreKernelAddress = false; 77 78 /** 79 * Waits for device to be online, marks the most recent boottime of the device 80 */ 81 @Before setUp()82 public void setUp() throws Exception { 83 getDevice().waitForDeviceAvailable(); 84 getDevice().disableAdbRoot(); 85 updateKernelStartTime(); 86 // TODO:(badash@): Watch for other things to track. 87 // Specifically time when app framework starts 88 89 sBuildInfo.put(getDevice(), getBuild()); 90 sAbi.put(getDevice(), getAbi()); 91 sTestName.put(getDevice(), testName.getMethodName()); 92 93 pocPusher.setDevice(getDevice()).setBuild(getBuild()).setAbi(getAbi()); 94 sPocPusher.put(getDevice(), pocPusher); 95 96 if (setKptr_restrict) { 97 if (getDevice().enableAdbRoot()) { 98 CLog.i("setting kptr_restrict to 2"); 99 getDevice().executeShellCommand("echo 2 > /proc/sys/kernel/kptr_restrict"); 100 getDevice().disableAdbRoot(); 101 } else { 102 // not a rootable device 103 ignoreKernelAddress = true; 104 } 105 } 106 } 107 108 /** 109 * Makes sure the phone is online, and the ensure the current boottime is within 2 seconds 110 * (due to rounding) of the previous boottime to check if The phone has crashed. 111 */ 112 @After tearDown()113 public void tearDown() throws Exception { 114 try { 115 getDevice().waitForDeviceAvailable(90 * 1000); 116 } catch (DeviceNotAvailableException e) { 117 // Force a disconnection of all existing sessions to see if that unsticks adbd. 118 getDevice().executeAdbCommand("reconnect"); 119 getDevice().waitForDeviceAvailable(30 * 1000); 120 } 121 122 long deviceTime = getDeviceUptime() + kernelStartTime; 123 long hostTime = System.currentTimeMillis() / 1000; 124 assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); 125 126 // TODO(badash@): add ability to catch runtime restart 127 } 128 getBuildInfo(ITestDevice device)129 public static IBuildInfo getBuildInfo(ITestDevice device) { 130 return sBuildInfo.get(device); 131 } 132 getAbi(ITestDevice device)133 public static IAbi getAbi(ITestDevice device) { 134 return sAbi.get(device); 135 } 136 getTestName(ITestDevice device)137 public static String getTestName(ITestDevice device) { 138 return sTestName.get(device); 139 } 140 getPocPusher(ITestDevice device)141 public static PocPusher getPocPusher(ITestDevice device) { 142 return sPocPusher.get(device); 143 } 144 145 // TODO convert existing assertMatches*() to RegexUtils.assertMatches*() 146 // b/123237827 147 @Deprecated assertMatches(String pattern, String input)148 public void assertMatches(String pattern, String input) throws Exception { 149 RegexUtils.assertContains(pattern, input); 150 } 151 152 @Deprecated assertMatchesMultiLine(String pattern, String input)153 public void assertMatchesMultiLine(String pattern, String input) throws Exception { 154 RegexUtils.assertContainsMultiline(pattern, input); 155 } 156 157 @Deprecated assertNotMatches(String pattern, String input)158 public void assertNotMatches(String pattern, String input) throws Exception { 159 RegexUtils.assertNotContains(pattern, input); 160 } 161 162 @Deprecated assertNotMatchesMultiLine(String pattern, String input)163 public void assertNotMatchesMultiLine(String pattern, String input) throws Exception { 164 RegexUtils.assertNotContainsMultiline(pattern, input); 165 } 166 167 /** 168 * Runs a provided function that collects a String to test against kernel pointer leaks. 169 * The getPtrFunction function implementation must return a String that starts with the 170 * pointer. i.e. "01234567". Trailing characters are allowed except for [0-9a-fA-F]. In 171 * the event that the pointer appears to be vulnerable, a JUnit assert is thrown. Since kernel 172 * pointers can be hashed, there is a possiblity the the hashed pointer overlaps into the 173 * normal kernel space. The test re-runs to make false positives statistically insignificant. 174 * When kernel pointers won't change without a reboot, provide a device to reboot. 175 * 176 * @param getPtrFunction a function that returns a string that starts with a pointer 177 * @param deviceToReboot device to reboot when kernel pointers won't change 178 */ assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)179 public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot) 180 throws Exception { 181 assumeFalse("Cannot set kptr_restrict to 2, ignoring kptr test.", ignoreKernelAddress); 182 String ptr = null; 183 for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive 184 ptr = getPtrFunction.call(); 185 if (ptr == null) { 186 return; 187 } 188 if (!isKptr(ptr)) { 189 // quit early because the ptr is likely hashed or zeroed. 190 return; 191 } 192 if (deviceToReboot != null) { 193 deviceToReboot.nonBlockingReboot(); 194 deviceToReboot.waitForDeviceAvailable(); 195 updateKernelStartTime(); 196 } 197 } 198 fail("\"" + ptr + "\" is an exposed kernel pointer."); 199 } 200 isKptr(String ptr)201 private boolean isKptr(String ptr) { 202 Matcher m = Pattern.compile("[0-9a-fA-F]*").matcher(ptr); 203 if (!m.find() || m.start() != 0) { 204 // ptr string is malformed 205 return false; 206 } 207 int length = m.end(); 208 209 if (length == 8) { 210 // 32-bit pointer 211 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 212 // 32-bit kernel memory range: 0xC0000000 -> 0xffffffff 213 // 0x3fffffff bytes = 1GB / 0xffffffff = 4 GB 214 // 1 in 4 collision for hashed pointers 215 return address.compareTo(new BigInteger("C0000000", RADIX_HEX)) >= 0; 216 } else if (length == 16) { 217 // 64-bit pointer 218 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 219 // 64-bit kernel memory range: 0x8000000000000000 -> 0xffffffffffffffff 220 // 48-bit implementation: 0xffff800000000000; 1 in 131,072 collision 221 // 56-bit implementation: 0xff80000000000000; 1 in 512 collision 222 // 64-bit implementation: 0x8000000000000000; 1 in 2 collision 223 return address.compareTo(new BigInteger("ff80000000000000", RADIX_HEX)) >= 0; 224 } 225 226 return false; 227 } 228 229 /** 230 * Check if a driver is present and readable. 231 */ containsDriver(ITestDevice device, String driver)232 protected boolean containsDriver(ITestDevice device, String driver) throws Exception { 233 return containsDriver(device, driver, true); 234 } 235 236 /** 237 * Check if a driver is present on a machine. 238 */ containsDriver(ITestDevice device, String driver, boolean checkReadable)239 protected boolean containsDriver(ITestDevice device, String driver, boolean checkReadable) 240 throws Exception { 241 boolean containsDriver = false; 242 if (driver.contains("*")) { 243 // -A list all files but . and .. 244 // -d directory, not contents 245 // -1 list one file per line 246 // -f unsorted 247 String ls = "ls -A -d -1 -f " + driver; 248 if (AdbUtils.runCommandGetExitCode(ls, device) == 0) { 249 String[] expanded = device.executeShellCommand(ls).split("\\R"); 250 for (String expandedDriver : expanded) { 251 containsDriver |= containsDriver(device, expandedDriver, checkReadable); 252 } 253 } 254 } else { 255 if(checkReadable) { 256 containsDriver = AdbUtils.runCommandGetExitCode("test -r " + driver, device) == 0; 257 } else { 258 containsDriver = AdbUtils.runCommandGetExitCode("test -e " + driver, device) == 0; 259 } 260 } 261 262 MetricsReportLog reportLog = buildMetricsReportLog(getDevice()); 263 reportLog.addValue("path", driver, ResultType.NEUTRAL, ResultUnit.NONE); 264 reportLog.addValue("exists", containsDriver, ResultType.NEUTRAL, ResultUnit.NONE); 265 reportLog.submit(); 266 267 return containsDriver; 268 } 269 buildMetricsReportLog(ITestDevice device)270 protected static MetricsReportLog buildMetricsReportLog(ITestDevice device) { 271 IBuildInfo buildInfo = getBuildInfo(device); 272 IAbi abi = getAbi(device); 273 String testName = getTestName(device); 274 275 StackTraceElement[] stacktraces = Thread.currentThread().getStackTrace(); 276 int stackDepth = 2; // 0: getStackTrace(), 1: buildMetricsReportLog, 2: caller 277 String className = stacktraces[stackDepth].getClassName(); 278 String methodName = stacktraces[stackDepth].getMethodName(); 279 String classMethodName = String.format("%s#%s", className, methodName); 280 281 // The stream name must be snake_case or else json formatting breaks 282 String streamName = methodName.replaceAll("(\\p{Upper})", "_$1").toLowerCase(); 283 284 MetricsReportLog reportLog = new MetricsReportLog( 285 buildInfo, 286 abi.getName(), 287 classMethodName, 288 "CtsSecurityBulletinHostTestCases", 289 streamName, 290 true); 291 reportLog.addValue("test_name", testName, ResultType.NEUTRAL, ResultUnit.NONE); 292 return reportLog; 293 } 294 getDeviceUptime()295 private long getDeviceUptime() throws DeviceNotAvailableException { 296 String uptime = null; 297 int attempts = 5; 298 do { 299 if (attempts-- <= 0) { 300 throw new RuntimeException("could not get device uptime"); 301 } 302 getDevice().waitForDeviceAvailable(); 303 uptime = getDevice().executeShellCommand("cat /proc/uptime").trim(); 304 } while (uptime.isEmpty()); 305 return Long.parseLong(uptime.substring(0, uptime.indexOf('.'))); 306 } 307 safeReboot()308 public void safeReboot() throws DeviceNotAvailableException { 309 getDevice().nonBlockingReboot(); 310 getDevice().waitForDeviceAvailable(); 311 updateKernelStartTime(); 312 } 313 314 /** 315 * Allows a test to pass if called after a planned reboot. 316 */ updateKernelStartTime()317 public void updateKernelStartTime() throws DeviceNotAvailableException { 318 long uptime = getDeviceUptime(); 319 kernelStartTime = (System.currentTimeMillis() / 1000) - uptime; 320 } 321 322 /** 323 * Return true if a module is play managed. 324 * 325 * Example of skipping a test based on mainline modules: 326 * <pre> 327 * @Test 328 * public void testPocCVE_1234_5678() throws Exception { 329 * // This will skip the test if MODULE_METADATA mainline module is play managed. 330 * assumeFalse(moduleIsPlayManaged("com.google.android.captiveportallogin")); 331 * // Do testing... 332 * } 333 * * </pre> 334 */ moduleIsPlayManaged(String modulePackageName)335 boolean moduleIsPlayManaged(String modulePackageName) throws Exception { 336 return mainlineModuleDetector.getPlayManagedModules().contains(modulePackageName); 337 } 338 assumeIsSupportedNfcDevice(ITestDevice device)339 public void assumeIsSupportedNfcDevice(ITestDevice device) throws Exception { 340 String supportedDrivers[] = { "/dev/nq-nci*", "/dev/pn54*", "/dev/pn551*", "/dev/pn553*", 341 "/dev/pn557*", "/dev/pn65*", "/dev/pn66*", "/dev/pn67*", 342 "/dev/pn80*", "/dev/pn81*", "/dev/sn100*", "/dev/sn220*", 343 "/dev/st54j*" }; 344 boolean isDriverFound = false; 345 for(String supportedDriver : supportedDrivers) { 346 if(containsDriver(device, supportedDriver, false)) { 347 isDriverFound = true; 348 break; 349 } 350 } 351 String[] output = device.executeShellCommand("ls -la /dev | grep nfc").split("\\n"); 352 String nfcDevice = null; 353 for (String line : output) { 354 if(line.contains("nfc")) { 355 String text[] = line.split("\\s+"); 356 nfcDevice = text[text.length - 1]; 357 } 358 } 359 assumeTrue("NFC device " + nfcDevice + " is not supported. Hence skipping the test", 360 isDriverFound); 361 } 362 } 363