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.tradefed.testtype.junit4.BaseHostJUnit4Test; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.device.NativeDevice; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 25 import com.android.ddmlib.Log; 26 27 import org.junit.After; 28 import org.junit.Before; 29 import org.junit.runner.RunWith; 30 31 import java.util.regex.Pattern; 32 import java.util.regex.Matcher; 33 import java.util.concurrent.Callable; 34 import java.math.BigInteger; 35 36 import static org.junit.Assert.*; 37 import static org.junit.Assume.*; 38 39 public class SecurityTestCase extends BaseHostJUnit4Test { 40 41 private static final String LOG_TAG = "SecurityTestCase"; 42 private static final int RADIX_HEX = 16; 43 44 protected static final int TIMEOUT_DEFAULT = 60; 45 // account for the poc timer of 5 minutes (+15 seconds for safety) 46 protected static final int TIMEOUT_NONDETERMINISTIC = 315; 47 48 private long kernelStartTime; 49 50 private HostsideOomCatcher oomCatcher = new HostsideOomCatcher(this); 51 private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this); 52 53 /** 54 * Waits for device to be online, marks the most recent boottime of the device 55 */ 56 @Before setUp()57 public void setUp() throws Exception { 58 getDevice().waitForDeviceAvailable(); 59 getDevice().disableAdbRoot(); 60 updateKernelStartTime(); 61 // TODO:(badash@): Watch for other things to track. 62 // Specifically time when app framework starts 63 64 oomCatcher.start(); 65 } 66 67 /** 68 * Makes sure the phone is online, and the ensure the current boottime is within 2 seconds 69 * (due to rounding) of the previous boottime to check if The phone has crashed. 70 */ 71 @After tearDown()72 public void tearDown() throws Exception { 73 oomCatcher.stop(getDevice().getSerialNumber()); 74 75 try { 76 getDevice().waitForDeviceAvailable(90 * 1000); 77 } catch (DeviceNotAvailableException e) { 78 // Force a disconnection of all existing sessions to see if that unsticks adbd. 79 getDevice().executeAdbCommand("reconnect"); 80 getDevice().waitForDeviceAvailable(30 * 1000); 81 } 82 83 if (oomCatcher.isOomDetected()) { 84 // we don't need to check kernel start time if we intentionally rebooted because oom 85 updateKernelStartTime(); 86 switch (oomCatcher.getOomBehavior()) { 87 case FAIL_AND_LOG: 88 fail("The device ran out of memory."); 89 break; 90 case PASS_AND_LOG: 91 Log.logAndDisplay(Log.LogLevel.INFO, LOG_TAG, "Skipping test."); 92 break; 93 case FAIL_NO_LOG: 94 fail(); 95 break; 96 } 97 } else { 98 long deviceTime = getDeviceUptime() + kernelStartTime; 99 long hostTime = System.currentTimeMillis() / 1000; 100 assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); 101 102 // TODO(badash@): add ability to catch runtime restart 103 } 104 } 105 106 // TODO convert existing assertMatches*() to RegexUtils.assertMatches*() 107 // b/123237827 108 @Deprecated assertMatches(String pattern, String input)109 public void assertMatches(String pattern, String input) throws Exception { 110 RegexUtils.assertContains(pattern, input); 111 } 112 113 @Deprecated assertMatchesMultiLine(String pattern, String input)114 public void assertMatchesMultiLine(String pattern, String input) throws Exception { 115 RegexUtils.assertContainsMultiline(pattern, input); 116 } 117 118 @Deprecated assertNotMatches(String pattern, String input)119 public void assertNotMatches(String pattern, String input) throws Exception { 120 RegexUtils.assertNotContains(pattern, input); 121 } 122 123 @Deprecated assertNotMatchesMultiLine(String pattern, String input)124 public void assertNotMatchesMultiLine(String pattern, String input) throws Exception { 125 RegexUtils.assertNotContainsMultiline(pattern, input); 126 } 127 128 /** 129 * Runs a provided function that collects a String to test against kernel pointer leaks. 130 * The getPtrFunction function implementation must return a String that starts with the 131 * pointer. i.e. "01234567". Trailing characters are allowed except for [0-9a-fA-F]. In 132 * the event that the pointer appears to be vulnerable, a JUnit assert is thrown. Since kernel 133 * pointers can be hashed, there is a possiblity the the hashed pointer overlaps into the 134 * normal kernel space. The test re-runs to make false positives statistically insignificant. 135 * When kernel pointers won't change without a reboot, provide a device to reboot. 136 * 137 * @param getPtrFunction a function that returns a string that starts with a pointer 138 * @param deviceToReboot device to reboot when kernel pointers won't change 139 */ assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)140 public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot) 141 throws Exception { 142 String ptr = null; 143 for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive 144 ptr = getPtrFunction.call(); 145 if (ptr == null) { 146 return; 147 } 148 if (!isKptr(ptr)) { 149 // quit early because the ptr is likely hashed or zeroed. 150 return; 151 } 152 if (deviceToReboot != null) { 153 deviceToReboot.nonBlockingReboot(); 154 deviceToReboot.waitForDeviceAvailable(); 155 updateKernelStartTime(); 156 } 157 } 158 fail("\"" + ptr + "\" is an exposed kernel pointer."); 159 } 160 isKptr(String ptr)161 private boolean isKptr(String ptr) { 162 Matcher m = Pattern.compile("[0-9a-fA-F]*").matcher(ptr); 163 if (!m.find() || m.start() != 0) { 164 // ptr string is malformed 165 return false; 166 } 167 int length = m.end(); 168 169 if (length == 8) { 170 // 32-bit pointer 171 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 172 // 32-bit kernel memory range: 0xC0000000 -> 0xffffffff 173 // 0x3fffffff bytes = 1GB / 0xffffffff = 4 GB 174 // 1 in 4 collision for hashed pointers 175 return address.compareTo(new BigInteger("C0000000", RADIX_HEX)) >= 0; 176 } else if (length == 16) { 177 // 64-bit pointer 178 BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX); 179 // 64-bit kernel memory range: 0x8000000000000000 -> 0xffffffffffffffff 180 // 48-bit implementation: 0xffff800000000000; 1 in 131,072 collision 181 // 56-bit implementation: 0xff80000000000000; 1 in 512 collision 182 // 64-bit implementation: 0x8000000000000000; 1 in 2 collision 183 return address.compareTo(new BigInteger("ff80000000000000", RADIX_HEX)) >= 0; 184 } 185 186 return false; 187 } 188 189 /** 190 * Check if a driver is present on a machine. 191 * deprecated: use AdbUtils.stat() instead! 192 */ 193 @Deprecated containsDriver(ITestDevice mDevice, String driver)194 protected boolean containsDriver(ITestDevice mDevice, String driver) throws Exception { 195 String result = mDevice.executeShellCommand("ls -Zl " + driver); 196 if(result.contains("No such file or directory")) { 197 return false; 198 } 199 return true; 200 } 201 getDeviceUptime()202 private long getDeviceUptime() throws DeviceNotAvailableException { 203 String uptime = getDevice().executeShellCommand("cat /proc/uptime"); 204 return Long.parseLong(uptime.substring(0, uptime.indexOf('.'))); 205 } 206 safeReboot()207 public void safeReboot() throws DeviceNotAvailableException { 208 getDevice().nonBlockingReboot(); 209 getDevice().waitForDeviceAvailable(); 210 updateKernelStartTime(); 211 } 212 213 /** 214 * Allows a test to pass if called after a planned reboot. 215 */ updateKernelStartTime()216 public void updateKernelStartTime() throws DeviceNotAvailableException { 217 long uptime = getDeviceUptime(); 218 kernelStartTime = (System.currentTimeMillis() / 1000) - uptime; 219 } 220 getOomCatcher()221 public HostsideOomCatcher getOomCatcher() { 222 return oomCatcher; 223 } 224 225 /** 226 * Return true if a module is play managed. 227 * 228 * Example of skipping a test based on mainline modules: 229 * <pre> 230 * @Test 231 * public void testPocCVE_1234_5678() throws Exception { 232 * // This will skip the test if MODULE_METADATA mainline module is play managed. 233 * assumeFalse(moduleIsPlayManaged("com.google.android.captiveportallogin")); 234 * // Do testing... 235 * } 236 * * </pre> 237 */ moduleIsPlayManaged(String modulePackageName)238 boolean moduleIsPlayManaged(String modulePackageName) throws Exception { 239 return mainlineModuleDetector.getPlayManagedModules().contains(modulePackageName); 240 } 241 } 242