Commit message: None
CVE: CVE-2018-9504
CWE: CWE-787
Vulnerability description: In sdp_copy_raw_data of sdp_discovery.cc, there is a possible out of bounds write due to an incorrect bounds check. This could lead to remote code execution over bluetooth with no additional execution privileges needed. User interaction is not needed for exploitation. Product: Android Versions: Android-7.0 Android-7.1.1 Android-7.1.2 Android-8.0 Android-8.1 Android-9.0 Android ID: A-110216176

Project: Android

static uint8_t* add_attr(uint8_t* p, tSDP_DISCOVERY_DB* p_db,
                         tSDP_DISC_REC* p_rec, uint16_t attr_id,
                         tSDP_DISC_ATTR* p_parent_attr, uint8_t nest_level) {
  tSDP_DISC_ATTR* p_attr;
 uint32_t attr_len;
 uint32_t total_len;
 uint16_t attr_type;
 uint16_t id;
 uint8_t type;
 uint8_t* p_end;
 uint8_t is_additional_list = nest_level & SDP_ADDITIONAL_LIST_MASK;

  nest_level &= ~(SDP_ADDITIONAL_LIST_MASK);

  type = *p++;
  p = sdpu_get_len_from_type(p, type, &attr_len);

  attr_len &= SDP_DISC_ATTR_LEN_MASK;
  attr_type = (type >> 3) & 0x0f;

 /* See if there is enough space in the database */
 if (attr_len > 4)
    total_len = attr_len - 4 + (uint16_t)sizeof(tSDP_DISC_ATTR);
 else
    total_len = sizeof(tSDP_DISC_ATTR);

 /* Ensure it is a multiple of 4 */
  total_len = (total_len + 3) & ~3;

 /* See if there is enough space in the database */
 if (p_db->mem_free < total_len) return (NULL);

  p_attr = (tSDP_DISC_ATTR*)p_db->p_free_mem;
  p_attr->attr_id = attr_id;
  p_attr->attr_len_type = (uint16_t)attr_len | (attr_type << 12);
  p_attr->p_next_attr = NULL;

 /* Store the attribute value */
 switch (attr_type) {
 case UINT_DESC_TYPE:
 if ((is_additional_list != 0) && (attr_len == 2)) {
        BE_STREAM_TO_UINT16(id, p);
 if (id != ATTR_ID_PROTOCOL_DESC_LIST)
          p -= 2;
 else {
 /* Reserve the memory for the attribute now, as we need to add
           * sub-attributes */
          p_db->p_free_mem += sizeof(tSDP_DISC_ATTR);
          p_db->mem_free -= sizeof(tSDP_DISC_ATTR);
          p_end = p + attr_len;
          total_len = 0;

 /* SDP_TRACE_DEBUG ("SDP - attr nest level:%d(list)", nest_level); */
 if (nest_level >= MAX_NEST_LEVELS) {
            SDP_TRACE_ERROR("SDP - attr nesting too deep");
 return (p_end);
 }

 /* Now, add the list entry */
          p = add_attr(p, p_db, p_rec, ATTR_ID_PROTOCOL_DESC_LIST, p_attr,
 (uint8_t)(nest_level + 1));

 break;
 }
 }
 /* Case falls through */

 case TWO_COMP_INT_DESC_TYPE:
 switch (attr_len) {
 case 1:
          p_attr->attr_value.v.u8 = *p++;
 break;
 case 2:
          BE_STREAM_TO_UINT16(p_attr->attr_value.v.u16, p);
 break;
 case 4:
          BE_STREAM_TO_UINT32(p_attr->attr_value.v.u32, p);
 break;
 default:
          BE_STREAM_TO_ARRAY(p, p_attr->attr_value.v.array, (int32_t)attr_len);
 break;
 }
 break;

 case UUID_DESC_TYPE:
 switch (attr_len) {
 case 2:
          BE_STREAM_TO_UINT16(p_attr->attr_value.v.u16, p);
 break;
 case 4:
          BE_STREAM_TO_UINT32(p_attr->attr_value.v.u32, p);
 if (p_attr->attr_value.v.u32 < 0x10000) {
            attr_len = 2;
            p_attr->attr_len_type = (uint16_t)attr_len | (attr_type << 12);
            p_attr->attr_value.v.u16 = (uint16_t)p_attr->attr_value.v.u32;
 }
 break;
 case 16:
 /* See if we can compress his UUID down to 16 or 32bit UUIDs */
 if (sdpu_is_base_uuid(p)) {
 if ((p[0] == 0) && (p[1] == 0)) {
              p_attr->attr_len_type =
 (p_attr->attr_len_type & ~SDP_DISC_ATTR_LEN_MASK) | 2;
              p += 2;
              BE_STREAM_TO_UINT16(p_attr->attr_value.v.u16, p);
              p += Uuid::kNumBytes128 - 4;
 } else {
              p_attr->attr_len_type =
 (p_attr->attr_len_type & ~SDP_DISC_ATTR_LEN_MASK) | 4;
              BE_STREAM_TO_UINT32(p_attr->attr_value.v.u32, p);
              p += Uuid::kNumBytes128 - 4;
 }
 } else {
            BE_STREAM_TO_ARRAY(p, p_attr->attr_value.v.array,
 (int32_t)attr_len);
 }
 break;
 default:
          SDP_TRACE_WARNING("SDP - bad len in UUID attr: %d", attr_len);
 return (p + attr_len);
 }
 break;

 case DATA_ELE_SEQ_DESC_TYPE:
 case DATA_ELE_ALT_DESC_TYPE:
 /* Reserve the memory for the attribute now, as we need to add
       * sub-attributes */
      p_db->p_free_mem += sizeof(tSDP_DISC_ATTR);
      p_db->mem_free -= sizeof(tSDP_DISC_ATTR);
      p_end = p + attr_len;
      total_len = 0;

 /* SDP_TRACE_DEBUG ("SDP - attr nest level:%d", nest_level); */
 if (nest_level >= MAX_NEST_LEVELS) {
        SDP_TRACE_ERROR("SDP - attr nesting too deep");
 return (p_end);
 }
 if (is_additional_list != 0 ||
          attr_id == ATTR_ID_ADDITION_PROTO_DESC_LISTS)
        nest_level |= SDP_ADDITIONAL_LIST_MASK;
 /* SDP_TRACE_DEBUG ("SDP - attr nest level:0x%x(finish)", nest_level); */

 while (p < p_end) {
 /* Now, add the list entry */
        p = add_attr(p, p_db, p_rec, 0, p_attr, (uint8_t)(nest_level + 1));

 if (!p) return (NULL);
 }
 break;

 case TEXT_STR_DESC_TYPE:
 case URL_DESC_TYPE:
      BE_STREAM_TO_ARRAY(p, p_attr->attr_value.v.array, (int32_t)attr_len);
 break;

 case BOOLEAN_DESC_TYPE:
 switch (attr_len) {
 case 1:
          p_attr->attr_value.v.u8 = *p++;
 break;
 default:
          SDP_TRACE_WARNING("SDP - bad len in boolean attr: %d", attr_len);
 return (p + attr_len);
 }
 break;

 default: /* switch (attr_type) */
 break;
 }

  p_db->p_free_mem += total_len;
  p_db->mem_free -= total_len;

 /* Add the attribute to the end of the chain */
 if (!p_parent_attr) {
 if (!p_rec->p_first_attr)
      p_rec->p_first_attr = p_attr;
 else {
      tSDP_DISC_ATTR* p_attr1 = p_rec->p_first_attr;

 while (p_attr1->p_next_attr) p_attr1 = p_attr1->p_next_attr;

      p_attr1->p_next_attr = p_attr;
 }
 } else {
 if (!p_parent_attr->attr_value.v.p_sub_attr) {
      p_parent_attr->attr_value.v.p_sub_attr = p_attr;
 /* SDP_TRACE_DEBUG ("parent:0x%x(id:%d), ch:0x%x(id:%d)",
          p_parent_attr, p_parent_attr->attr_id, p_attr, p_attr->attr_id); */
 } else {
      tSDP_DISC_ATTR* p_attr1 = p_parent_attr->attr_value.v.p_sub_attr;
 /* SDP_TRACE_DEBUG ("parent:0x%x(id:%d), ch1:0x%x(id:%d)",
          p_parent_attr, p_parent_attr->attr_id, p_attr1, p_attr1->attr_id); */

 while (p_attr1->p_next_attr) p_attr1 = p_attr1->p_next_attr;

      p_attr1->p_next_attr = p_attr;
 /* SDP_TRACE_DEBUG ("new ch:0x%x(id:%d)", p_attr, p_attr->attr_id); */
 }
 }

 return (p);
}

Project: Android

tSDP_DISC_REC* add_record(tSDP_DISCOVERY_DB* p_db, const RawAddress& p_bda) {
  tSDP_DISC_REC* p_rec;

 /* See if there is enough space in the database */
 if (p_db->mem_free < sizeof(tSDP_DISC_REC)) return (NULL);

  p_rec = (tSDP_DISC_REC*)p_db->p_free_mem;
  p_db->p_free_mem += sizeof(tSDP_DISC_REC);
  p_db->mem_free -= sizeof(tSDP_DISC_REC);

  p_rec->p_first_attr = NULL;
  p_rec->p_next_rec = NULL;

  p_rec->remote_bd_addr = p_bda;

 /* Add the record to the end of chain */
 if (!p_db->p_first_rec)
    p_db->p_first_rec = p_rec;
 else {
    tSDP_DISC_REC* p_rec1 = p_db->p_first_rec;

 while (p_rec1->p_next_rec) p_rec1 = p_rec1->p_next_rec;

    p_rec1->p_next_rec = p_rec;
 }

 return (p_rec);
}

Project: Android

static void process_service_attr_rsp(tCONN_CB* p_ccb, uint8_t* p_reply,
 uint8_t* p_reply_end) {
 uint8_t *p_start, *p_param_len;
 uint16_t param_len, list_byte_count;
 bool cont_request_needed = false;

#if (SDP_DEBUG_RAW == TRUE)
  SDP_TRACE_WARNING("process_service_attr_rsp raw inc:%d",
                    SDP_RAW_DATA_INCLUDED);
#endif
 /* If p_reply is NULL, we were called after the records handles were read */
 if (p_reply) {
#if (SDP_DEBUG_RAW == TRUE)
    SDP_TRACE_WARNING("ID & len: 0x%02x-%02x-%02x-%02x", p_reply[0], p_reply[1],
                      p_reply[2], p_reply[3]);
#endif
 /* Skip transaction ID and length */
    p_reply += 4;

    BE_STREAM_TO_UINT16(list_byte_count, p_reply);
#if (SDP_DEBUG_RAW == TRUE)
    SDP_TRACE_WARNING("list_byte_count:%d", list_byte_count);
#endif

 /* Copy the response to the scratchpad. First, a safety check on the length
     */
 if ((p_ccb->list_len + list_byte_count) > SDP_MAX_LIST_BYTE_COUNT) {
      sdp_disconnect(p_ccb, SDP_INVALID_PDU_SIZE);
 return;
 }

#if (SDP_DEBUG_RAW == TRUE)
    SDP_TRACE_WARNING("list_len: %d, list_byte_count: %d", p_ccb->list_len,
                      list_byte_count);
#endif
 if (p_ccb->rsp_list == NULL)
      p_ccb->rsp_list = (uint8_t*)osi_malloc(SDP_MAX_LIST_BYTE_COUNT);
    memcpy(&p_ccb->rsp_list[p_ccb->list_len], p_reply, list_byte_count);
    p_ccb->list_len += list_byte_count;
    p_reply += list_byte_count;
#if (SDP_DEBUG_RAW == TRUE)
    SDP_TRACE_WARNING("list_len: %d(attr_rsp)", p_ccb->list_len);

 /* Check if we need to request a continuation */
    SDP_TRACE_WARNING("*p_reply:%d(%d)", *p_reply, SDP_MAX_CONTINUATION_LEN);
#endif
 if (*p_reply) {
 if (*p_reply > SDP_MAX_CONTINUATION_LEN) {
        sdp_disconnect(p_ccb, SDP_INVALID_CONT_STATE);
 return;
 }
      cont_request_needed = true;
 } else {
#if (SDP_RAW_DATA_INCLUDED == TRUE)
      SDP_TRACE_WARNING("process_service_attr_rsp");
      sdp_copy_raw_data(p_ccb, false);
#endif

 /* Save the response in the database. Stop on any error */
 if (!save_attr_seq(p_ccb, &p_ccb->rsp_list[0],
 &p_ccb->rsp_list[p_ccb->list_len])) {
        sdp_disconnect(p_ccb, SDP_DB_FULL);
 return;
 }
      p_ccb->list_len = 0;
      p_ccb->cur_handle++;
 }
 }

 /* Now, ask for the next handle. Re-use the buffer we just got. */
 if (p_ccb->cur_handle < p_ccb->num_handles) {
    BT_HDR* p_msg = (BT_HDR*)osi_malloc(SDP_DATA_BUF_SIZE);
 uint8_t* p;

    p_msg->offset = L2CAP_MIN_OFFSET;
    p = p_start = (uint8_t*)(p_msg + 1) + L2CAP_MIN_OFFSET;

 /* Get all the attributes from the server */
    UINT8_TO_BE_STREAM(p, SDP_PDU_SERVICE_ATTR_REQ);
    UINT16_TO_BE_STREAM(p, p_ccb->transaction_id);
    p_ccb->transaction_id++;

 /* Skip the length, we need to add it at the end */
    p_param_len = p;
    p += 2;

    UINT32_TO_BE_STREAM(p, p_ccb->handles[p_ccb->cur_handle]);

 /* Max attribute byte count */
    UINT16_TO_BE_STREAM(p, sdp_cb.max_attr_list_size);

 /* If no attribute filters, build a wildcard attribute sequence */
 if (p_ccb->p_db->num_attr_filters)
      p = sdpu_build_attrib_seq(p, p_ccb->p_db->attr_filters,
                                p_ccb->p_db->num_attr_filters);
 else
      p = sdpu_build_attrib_seq(p, NULL, 0);

 /* Was this a continuation request ? */
 if (cont_request_needed) {
 if ((p_reply + *p_reply + 1) <= p_reply_end) {
        memcpy(p, p_reply, *p_reply + 1);
        p += *p_reply + 1;
 } else {
        android_errorWriteLog(0x534e4554, "68161546");
 }
 } else
      UINT8_TO_BE_STREAM(p, 0);

 /* Go back and put the parameter length into the buffer */
    param_len = (uint16_t)(p - p_param_len - 2);
    UINT16_TO_BE_STREAM(p_param_len, param_len);

 /* Set the length of the SDP data in the buffer */
    p_msg->len = (uint16_t)(p - p_start);

    L2CA_DataWrite(p_ccb->connection_id, p_msg);

 /* Start inactivity timer */
    alarm_set_on_mloop(p_ccb->sdp_conn_timer, SDP_INACT_TIMEOUT_MS,
                       sdp_conn_timer_timeout, p_ccb);
 } else {
    sdp_disconnect(p_ccb, SDP_SUCCESS);
 return;
 }
}

Project: Android

static void process_service_search_rsp(tCONN_CB* p_ccb, uint8_t* p_reply,
 uint8_t* p_reply_end) {
 uint16_t xx;
 uint16_t total, cur_handles, orig;
 uint8_t cont_len;

 /* Skip transaction, and param len */
  p_reply += 4;
  BE_STREAM_TO_UINT16(total, p_reply);
  BE_STREAM_TO_UINT16(cur_handles, p_reply);

  orig = p_ccb->num_handles;
  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;

 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);
 }
}

Project: Android

static uint8_t* save_attr_seq(tCONN_CB* p_ccb, uint8_t* p, uint8_t* p_msg_end) {
 uint32_t seq_len, attr_len;
 uint16_t attr_id;
 uint8_t type, *p_seq_end;
  tSDP_DISC_REC* p_rec;

  type = *p++;

 if ((type >> 3) != DATA_ELE_SEQ_DESC_TYPE) {
    SDP_TRACE_WARNING("SDP - Wrong type: 0x%02x in attr_rsp", type);
 return (NULL);
 }

  p = sdpu_get_len_from_type(p, type, &seq_len);
 if ((p + seq_len) > p_msg_end) {
    SDP_TRACE_WARNING("SDP - Bad len in attr_rsp %d", seq_len);
 return (NULL);
 }

 /* Create a record */
  p_rec = add_record(p_ccb->p_db, p_ccb->device_address);
 if (!p_rec) {
    SDP_TRACE_WARNING("SDP - DB full add_record");
 return (NULL);
 }

  p_seq_end = p + seq_len;

 while (p < p_seq_end) {
 /* First get the attribute ID */
    type = *p++;
    p = sdpu_get_len_from_type(p, type, &attr_len);
 if (((type >> 3) != UINT_DESC_TYPE) || (attr_len != 2)) {
      SDP_TRACE_WARNING("SDP - Bad type: 0x%02x or len: %d in attr_rsp", type,
                        attr_len);
 return (NULL);
 }
    BE_STREAM_TO_UINT16(attr_id, p);

 /* Now, add the attribute value */
    p = add_attr(p, p_ccb->p_db, p_rec, attr_id, NULL, 0);

 if (!p) {
      SDP_TRACE_WARNING("SDP - DB full add_attr");
 return (NULL);
 }
 }

 return (p);
}

Project: Android

void sdp_disc_connected(tCONN_CB* p_ccb) {
 if (p_ccb->is_attr_search) {
    p_ccb->disc_state = SDP_DISC_WAIT_SEARCH_ATTR;

    process_service_search_attr_rsp(p_ccb, NULL, NULL);
 } else {
 /* First step is to get a list of the handles from the server. */
 /* We are not searching for a specific attribute, so we will   */
 /* first search for the service, then get all attributes of it */

    p_ccb->num_handles = 0;
    sdp_snd_service_search_req(p_ccb, 0, NULL);
 }
}

Project: Android

void sdp_disc_server_rsp(tCONN_CB* p_ccb, BT_HDR* p_msg) {
 uint8_t *p, rsp_pdu;
 bool invalid_pdu = true;

#if (SDP_DEBUG_RAW == TRUE)
  SDP_TRACE_WARNING("sdp_disc_server_rsp disc_state:%d", p_ccb->disc_state);
#endif

 /* stop inactivity timer when we receive a response */
  alarm_cancel(p_ccb->sdp_conn_timer);

 /* Got a reply!! Check what we got back */
  p = (uint8_t*)(p_msg + 1) + p_msg->offset;
 uint8_t* p_end = p + p_msg->len;

  BE_STREAM_TO_UINT8(rsp_pdu, p);

  p_msg->len--;

 switch (rsp_pdu) {
 case SDP_PDU_SERVICE_SEARCH_RSP:
 if (p_ccb->disc_state == SDP_DISC_WAIT_HANDLES) {
        process_service_search_rsp(p_ccb, p, p_end);
        invalid_pdu = false;
 }
 break;

 case SDP_PDU_SERVICE_ATTR_RSP:
 if (p_ccb->disc_state == SDP_DISC_WAIT_ATTR) {
        process_service_attr_rsp(p_ccb, p, p_end);
        invalid_pdu = false;
 }
 break;

 case SDP_PDU_SERVICE_SEARCH_ATTR_RSP:
 if (p_ccb->disc_state == SDP_DISC_WAIT_SEARCH_ATTR) {
        process_service_search_attr_rsp(p_ccb, p, p_end);
        invalid_pdu = false;
 }
 break;
 }

 if (invalid_pdu) {
    SDP_TRACE_WARNING("SDP - Unexp. PDU: %d in state: %d", rsp_pdu,
                      p_ccb->disc_state);
    sdp_disconnect(p_ccb, SDP_GENERIC_ERROR);
 }
}

Project: Android

static void sdp_snd_service_search_req(tCONN_CB* p_ccb, uint8_t cont_len,
 uint8_t* p_cont) {
 uint8_t *p, *p_start, *p_param_len;
  BT_HDR* p_cmd = (BT_HDR*)osi_malloc(SDP_DATA_BUF_SIZE);
 uint16_t param_len;

 /* Prepare the buffer for sending the packet to L2CAP */
  p_cmd->offset = L2CAP_MIN_OFFSET;
  p = p_start = (uint8_t*)(p_cmd + 1) + L2CAP_MIN_OFFSET;

 /* Build a service search request packet */
  UINT8_TO_BE_STREAM(p, SDP_PDU_SERVICE_SEARCH_REQ);
  UINT16_TO_BE_STREAM(p, p_ccb->transaction_id);
  p_ccb->transaction_id++;

 /* Skip the length, we need to add it at the end */
  p_param_len = p;
  p += 2;

/* Build the UID sequence. */
#if (SDP_BROWSE_PLUS == TRUE)
  p = sdpu_build_uuid_seq(p, 1,
 &p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx]);
#else
  p = sdpu_build_uuid_seq(p, p_ccb->p_db->num_uuid_filters,
                          p_ccb->p_db->uuid_filters);
#endif

 /* Set max service record count */
  UINT16_TO_BE_STREAM(p, sdp_cb.max_recs_per_search);

 /* Set continuation state */
  UINT8_TO_BE_STREAM(p, cont_len);

 /* if this is not the first request */
 if (cont_len && p_cont) {
    memcpy(p, p_cont, cont_len);
    p += cont_len;
 }

 /* Go back and put the parameter length into the buffer */
  param_len = (uint16_t)(p - p_param_len - 2);
  UINT16_TO_BE_STREAM(p_param_len, param_len);

  p_ccb->disc_state = SDP_DISC_WAIT_HANDLES;

 /* Set the length of the SDP data in the buffer */
  p_cmd->len = (uint16_t)(p - p_start);

#if (SDP_DEBUG_RAW == TRUE)
  SDP_TRACE_WARNING("sdp_snd_service_search_req cont_len :%d disc_state:%d",
                    cont_len, p_ccb->disc_state);
#endif

  L2CA_DataWrite(p_ccb->connection_id, p_cmd);

 /* Start inactivity timer */
  alarm_set_on_mloop(p_ccb->sdp_conn_timer, SDP_INACT_TIMEOUT_MS,
                     sdp_conn_timer_timeout, p_ccb);
}