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