CTS/STS Add kernel pointer leak assertions

Usage:
assertNotKernelPointer(() -> {
    return runPoc("myKernelLeakPoc")
}, getDevice() /* device to reboot if needed */);

Bug: 115528015
Test: as follows:
        boolean pass = false;
        assertNotKernelPointer(() -> {return "BFFFFFFF";}, null);
        assertNotKernelPointer(() -> {return "bfffffff";}, null);
        try {assertNotKernelPointer(() -> {return "C0000000";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "c0000000";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "C0000001";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "c0000001";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        assertNotKernelPointer(() -> {return "BFFFFFFF fdjksal;";}, null);
        assertNotKernelPointer(() -> {return "bfffffff fdjksal;";}, null);
        try {assertNotKernelPointer(() -> {return "C0000000 fdsa";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "C0000001 fdsiaoe";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "ffff800000000000";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "FFFF800000000000";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        assertNotKernelPointer(() -> {return "7fffffffffffffff";}, null);
        assertNotKernelPointer(() -> {return "7FFFFFFFFFFFFFFF";}, null);
        try {assertNotKernelPointer(() -> {return "ffff800000000001";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        try {assertNotKernelPointer(() -> {return "FFFF800000000001";}, null);} catch (junit.framework.AssertionFailedError e){pass = true;} finally {assertTrue(pass); pass = false;}
        assertNotKernelPointer(() -> {return "not a pointer at all";}, null);
        assertNotKernelPointer(() -> {return "0";}, null);
        assertNotKernelPointer(() -> {return "00000000";}, null);
        assertNotKernelPointer(() -> {return "0000000000000000";}, null);
        assertNotKernelPointer(() -> {return "  (null)";}, null);
        assertNotKernelPointer(() -> {return "          (null)";}, null);
        assertNotKernelPointer(() -> {return "   (nil)";}, null);
        assertNotKernelPointer(() -> {return "           (nil)";}, null);

Change-Id: If6eba3457d543b24fcfb4429992a2fee573e0b3b
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 7197b92..dfb0ea7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -35,10 +35,13 @@
 import java.util.Map;
 import java.util.HashMap;
 import com.android.ddmlib.Log;
+import java.util.concurrent.Callable;
+import java.math.BigInteger;
 
 public class SecurityTestCase extends DeviceTestCase {
 
     private static final String LOG_TAG = "SecurityTestCase";
+    private static final int RADIX_HEX = 16;
 
     private long kernelStartTime;
 
@@ -147,4 +150,64 @@
         assertFalse("Pattern found: " + pattern,
           Pattern.compile(pattern, Pattern.DOTALL|Pattern.MULTILINE).matcher(input).find());
     }
+
+    /**
+     * Runs a provided function that collects a String to test against kernel pointer leaks.
+     * The getPtrFunction function implementation must return a String that starts with the
+     * pointer. i.e. "01234567". Trailing characters are allowed except for [0-9a-fA-F]. In
+     * the event that the pointer appears to be vulnerable, a JUnit assert is thrown. Since kernel
+     * pointers can be hashed, there is a possiblity the the hashed pointer overlaps into the
+     * normal kernel space. The test re-runs to make false positives statistically insignificant.
+     * When kernel pointers won't change without a reboot, provide a device to reboot.
+     *
+     * @param getPtrFunction a function that returns a string that starts with a pointer
+     * @param deviceToReboot device to reboot when kernel pointers won't change
+     */
+    public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)
+            throws Exception {
+        String ptr = null;
+        for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive
+            ptr = getPtrFunction.call();
+            if (ptr == null) {
+                return;
+            }
+            if (!isKptr(ptr)) {
+                // quit early because the ptr is likely hashed or zeroed.
+                return;
+            }
+            if (deviceToReboot != null) {
+                deviceToReboot.nonBlockingReboot();
+                deviceToReboot.waitForDeviceAvailable();
+            }
+        }
+        fail("\"" + ptr + "\" is an exposed kernel pointer.");
+    }
+
+    private boolean isKptr(String ptr) {
+        Matcher m = Pattern.compile("[0-9a-fA-F]*").matcher(ptr);
+        if (!m.find() || m.start() != 0) {
+           // ptr string is malformed
+           return false;
+        }
+        int length = m.end();
+
+        if (length == 8) {
+          // 32-bit pointer
+          BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX);
+          // 32-bit kernel memory range: 0xC0000000 -> 0xffffffff
+          // 0x3fffffff bytes = 1GB /  0xffffffff = 4 GB
+          // 1 in 4 collision for hashed pointers
+          return address.compareTo(new BigInteger("C0000000", RADIX_HEX)) >= 0;
+        } else if (length == 16) {
+          // 64-bit pointer
+          BigInteger address = new BigInteger(ptr.substring(0, length), RADIX_HEX);
+          // 64-bit kernel memory range: 0x8000000000000000 -> 0xffffffffffffffff
+          // 48-bit implementation: 0xffff800000000000; 1 in 131,072 collision
+          // 56-bit implementation: 0xff80000000000000; 1 in 512 collision
+          // 64-bit implementation: 0x8000000000000000; 1 in 2 collision
+          return address.compareTo(new BigInteger("ff80000000000000", RADIX_HEX)) >= 0;
+        }
+
+        return false;
+    }
 }