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