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