Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=958

The following code in frameworks/opt/net/wifi/service/jni/com_android_server_wifi_WifiNative.cpp doesn't validate the parameter params.num_bssid, and then copies that number of elements into a stack-allocated wifi_bssid_hotlist_params structure. I don't think this can be reached from an untrusted_app context; but it can be reached from a context with system_api_service access; so a compromised platform app or one of several lower privileged system services (bluetooth, nfc etc.).

static jboolean android_net_wifi_setHotlist(
        JNIEnv *env, jclass cls, jint iface, jint id, jobject ap)  {

    JNIHelper helper(env);
    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
    ALOGD("setting hotlist on interface[%d] = %p", iface, handle);

    wifi_bssid_hotlist_params params;
    memset(&params, 0, sizeof(params));

    params.lost_ap_sample_size = helper.getIntField(ap, "apLostThreshold");

    JNIObject<jobjectArray> array = helper.getArrayField(
            ap, "bssidInfos", "[Landroid/net/wifi/WifiScanner$BssidInfo;");
    params.num_bssid = helper.getArrayLength(array);

    if (params.num_bssid == 0) {
        ALOGE("setHotlist array length was 0");
        return false;
    }

    for (int i = 0; i < params.num_bssid; i++) { // <--- no validation on num_bssid
        JNIObject<jobject> objAp = helper.getObjectArrayElement(array, i);

        JNIObject<jstring> macAddrString = helper.getStringField(objAp, "bssid");
        if (macAddrString == NULL) {
            ALOGE("Error getting bssid field");
            return false;
        }

        ScopedUtfChars chars(env, macAddrString);
        const char *bssid = chars.c_str();
        if (bssid == NULL) {
            ALOGE("Error getting bssid");
            return false;
        }
        parseMacAddress(bssid, params.ap[i].bssid); // <--- params.ap has 128 elements.

        mac_addr addr;
        memcpy(addr, params.ap[i].bssid, sizeof(mac_addr));

        char bssidOut[32];
        snprintf(bssidOut, sizeof(bssidOut), "%0x:%0x:%0x:%0x:%0x:%0x", addr[0],
                 addr[1], addr[2], addr[3], addr[4], addr[5]);

        ALOGD("Added bssid %s", bssidOut);

        params.ap[i].low = helper.getIntField(objAp, "low");
        params.ap[i].high = helper.getIntField(objAp, "high");
    }

See attached for a POC which causes a crash before the function with the corrupted stack frame returns and checks the stack cookie.

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
[---------------------------------------------------------------------REGISTERS----------------------------------------------------------------------]
*X0   0x80000000 <-- 0x0
*X1   0x0
*X2   0x707882c3e0 <-- u'c0:1d:b3:3f:01:...'
*X3   0x3
*X4   0x709bf05fc0 <-- stp    x28, x27, [sp, #-0x60]!
*X5   0x709c1f07b0 (art::gJniNativeInterface) <-- 0x0
*X6   0x709bf27034 <-- cbz    x2, #0x709bf27040 /* u'b' */
*X7   0x284801ff284800ff
*X8   0xc01d0142c01d0229
*X9   0x1
*X10  0xc01d0142c01d0141
*X11  0x7082dff4e8 <-- 0x41013fb31dc0
 X12  0x0
*X13  0x0
*X14  0x0
*X15  0x33511e057221be
*X16  0x709f0035a0 (pthread_getspecific@got.plt) --> 0x709efaad5c (pthread_getspecific) <-- movz   w8, #0x8000, lsl #16
*X17  0x709efaad5c (pthread_getspecific) <-- movz   w8, #0x8000, lsl #16
*X18  0x0
*X19  0x707882c3e0 <-- u'c0:1d:b3:3f:01:...'
*X20  0x7082dfe0a0 --> 0x70833c1470 --> 0x7083381c0c (android::JNIObject<_jobject*>::~JNIObject()) <-- adrp   x2, #0x70833c2000
*X21  0x7082dfe0b8 --> 0x70833c1490 --> 0x7083381c70 (android::JNIObject<_jstring*>::~JNIObject()) <-- adrp   x2, #0x70833c2000
*X22  0x7082dfe078 <-- 0x0
*X23  0xb1da807287fa8cf
*X24  0x709f00e86c (je_tsd_tsd) <-- 0xa880000000
*X25  0x7082dfe8d8 <-- u'c0:1d:b3:3f:1:4...'
*X26  0x200011
*X27  0x7082dfe0d0 <-- 0x100000000001
*X28  0x707882c3e0 <-- u'c0:1d:b3:3f:01:...'
*SP   0x70815310f0 <-- 0x0
*PC   0x709efaada8 (pthread_getspecific+76) <-- ldr    x10, [x10, #0xe0]
[------------------------------------------------------------------------CODE------------------------------------------------------------------------]
 => 0x709efaada8L <pthread_getspecific+76>    ldr    x10, [x10, #0xe0]
    0x709efaadacL <pthread_getspecific+80>    cmp    x10, x9
    0x709efaadb0L <pthread_getspecific+84>    b.ne   #pthread_getspecific+56       <0x709efaad94>
...
    0x709efaad94L <pthread_getspecific+56>    mov    x0, xzr
    0x709efaad98L <pthread_getspecific+60>    str    xzr, [x8]
    0x709efaad9cL <pthread_getspecific+64>    ret

    0x709efaada0L <pthread_getspecific+68>    add    x10, x10, x8, lsl #4
    0x709efaada4L <pthread_getspecific+72>    add    x8, x10, #0xe8
 => 0x709efaada8L <pthread_getspecific+76>    ldr    x10, [x10, #0xe0]
    0x709efaadacL <pthread_getspecific+80>    cmp    x10, x9
    0x709efaadb0L <pthread_getspecific+84>    b.ne   #pthread_getspecific+56       <0x709efaad94>
[------------------------------------------------------------------------CODE------------------------------------------------------------------------]
155	in bionic/libc/bionic/pthread_key.cpp
[-----------------------------------------------------------------------STACK------------------------------------------------------------------------]
00:0000| sp  0x70815310f0 <-- 0x0
...
04:0020|     0x7081531110 --> 0x3f800000 <-- 0x0
05:0028|     0x7081531118 <-- 0x0
...
[---------------------------------------------------------------------BACKTRACE----------------------------------------------------------------------]
>  f 0       709efaada8 pthread_getspecific+76
   f 1       709efd2394 je_free+68
   f 2       709efd2394 je_free+68
   f 3       709efd2394 je_free+68
   f 4       709efd2394 je_free+68
   f 5       7083387d10
   f 6       7083387d10
   f 7       7083387d10
Program received signal SIGSEGV (fault address 0x1d0142c01d0221)
pwndbg> bt
#0  pthread_getspecific (key=<optimized out>) at bionic/libc/bionic/pthread_key.cpp:160
#1  0x000000709efd2394 in je_tsd_wrapper_get () at external/jemalloc/include/jemalloc/internal/tsd.h:609
#2  je_tsd_get () at external/jemalloc/include/jemalloc/internal/tsd.h:609
#3  je_tsd_fetch () at external/jemalloc/include/jemalloc/internal/tsd.h:614
#4  je_free (ptr=0x707882c3e0) at external/jemalloc/src/jemalloc.c:1932
#5  0x0000007083387d10 in _JNIEnv::ReleaseStringUTFChars (utf=0x707882c3e0 "c0:1d:b3:3f:01:"..., string=0x200011, this=0x7091fd2b00) at libnativehelper/include/nativehelper/jni.h:851
#6  ScopedUtfChars::~ScopedUtfChars (this=<synthetic pointer>, __in_chrg=<optimized out>) at libnativehelper/include/nativehelper/ScopedUtfChars.h:45
#7  android::android_net_wifi_setHotlist (env=0x7091fd2b00, cls=<optimized out>, iface=<optimized out>, id=0x690a3633, ap=<optimized out>) at frameworks/opt/net/wifi/service/jni/com_android_server_wifi_WifiNative.cpp:799
#8  0x000000709b1a084c in ?? ()

Fixed in https://source.android.com/security/bulletin/2016-12-01.html


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40945.zip