2023.09.15

CVE-2022-20445 A-225876506 ID High 10, 11, 12, 12L, 13

patch

分析

没有校验packet中的cur_handles,导致后续的三次整数溢出。但是我没有明白哪里会溢出啊,[5]处的循环根本进不去啊。。。

// packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* Define the SDP Connection Control Block */
struct tCONN_CB {
  ......
  uint32_t
      handles[SDP_MAX_DISC_SERVER_RECS]; /* Discovered server record handles */
  uint16_t num_handles;                  /* Number of server handles     */
  uint16_t cur_handle;                   /* Current handle being processed */
  ......
};
/******************************************************************************
 *
 * Function         process_service_search_rsp
 *
 * Description      This function is called when there is a search response from
 *                  the server.
 *
 * Returns          void
 *
 ******************************************************************************/
static void process_service_search_rsp(tCONN_CB* p_ccb, uint8_t* p_reply,
uint8_t* p_reply_end) { // p_reply指向本次解析的packet的开头;p_reply_end指向本次解析的packet的结尾
    uint16_t xx;
    uint16_t total, cur_handles, orig;
    uint8_t cont_len;
    if (p_reply + 8 > p_reply_end) {
        android_errorWriteLog(0x534e4554, "74249842");
        sdp_disconnect(p_ccb, SDP_GENERIC_ERROR);
        return;
    }
    /* Skip transaction, and param len */
    p_reply += 4;
    BE_STREAM_TO_UINT16(total, p_reply);
    // [1] cur_handles从packet中读取,可控,取值范围为0~UINT16_MAX。该变量代表本次解析需要处理的handles数量
    BE_STREAM_TO_UINT16(cur_handles, p_reply);
    // [2] orig的类型是uint16_t。变量orig表示之前的解析过程中处理过的handles总数
    orig = p_ccb->num_handles;
    // [3] 这行代码的目的是计算:加上本次解析,一共需要解析的handles数量
    // 如果cur_handles足够大(比如前面读取的时候传入负数),那么这里将会发生整数回绕,p_ccb->num_handles < orig
    p_ccb->num_handles += cur_handles;
    if (p_ccb->num_handles == 0) {
        SDP_TRACE_WARNING("SDP - Rcvd ServiceSearchRsp, no matches");
        sdp_disconnect(p_ccb, SDP_NO_RECS_MATCH);
        return;
    }
    /* Save the handles that match. We will can only process a certain number. */
    if (total > sdp_cb.max_recs_per_search) total = sdp_cb.max_recs_per_search;
    if (p_ccb->num_handles > sdp_cb.max_recs_per_search)
        p_ccb->num_handles = sdp_cb.max_recs_per_search;
    // 这个if判断的含义是:计算一个偏移量,以判断需要解析的数据是否超过 p_reply_end,即判断是否会发生越界。
    // 符号 > 左边的表达式用于计算一个指针的偏移量。具体来说,它计算了从指针p_reply开始,向后移动的字节数。
    // 逐步理解:(p_ccb->num_handles - orig) 的值其实就是cur_handles,代表本次解析需要处理的handles数量
    // ((p_ccb->num_handles - orig) * 4):乘以4是因为handle的长度是4个字节(可以查看p_ccb->handles[]数组,该数组的类型是uint32_t)
    // p_reply + ((p_ccb->num_handles - orig) * 4):将p_reply往后移动((p_ccb->num_handles - orig) * 4)个字节,然后就能理解if判断的作用了
    
    // [4] 再次发生两次整数溢出:
    //    [4.1] p_ccb->num_handles - orig 的结果其实是cur_handles,一个很大的值
    //    [4.2] p_reply + ((p_ccb->num_handles - orig) * 4) + 1,发生整数回绕后,变得很小,从而绕过该if检查
    if (p_reply + ((p_ccb->num_handles - orig) * 4) + 1 > p_reply_end) {
        android_errorWriteLog(0x534e4554, "74249842");
        sdp_disconnect(p_ccb, SDP_GENERIC_ERROR);
        return;
    }
    // [5] 如果按照前面的溢出情况,xx > p_ccb->num_handles,不会进循环体里啊。。。。哪里溢出了。。。
    for (xx = orig; xx < p_ccb->num_handles; xx++)
        BE_STREAM_TO_UINT32(p_ccb->handles[xx], p_reply);
    BE_STREAM_TO_UINT8(cont_len, p_reply);
    if (cont_len != 0) {
        if (cont_len > SDP_MAX_CONTINUATION_LEN) {
            sdp_disconnect(p_ccb, SDP_INVALID_CONT_STATE);
            return;
        }
        if (p_reply + cont_len > p_reply_end) {
            android_errorWriteLog(0x534e4554, "68161546");
            sdp_disconnect(p_ccb, SDP_INVALID_CONT_STATE);
            return;
        }
        /* stay in the same state */
        sdp_snd_service_search_req(p_ccb, cont_len, p_reply);
    } else {
        /* change state */
        p_ccb->disc_state = SDP_DISC_WAIT_ATTR;
        /* Kick off the first attribute request */
        process_service_attr_rsp(p_ccb, NULL, NULL);
    }
}

测试[4]中的两次整数溢出:

// 假设p_ccb->num_handles - orig == xx
#include<stdio.h>
#include<stdint.h>

void main(){
    uint16_t xx = 16384;
    uint16_t a = (xx*4) + 1;
    printf("%hu\n", a);
}

编译执行后,结果如下:

❯ ./a.out
1