CVE-2026-0059 AI-generated analysis
platform/packages/modules/Bluetooth, system/stack/sdp (SDP client request builder: sdp_discovery.cc / sdp_utils.cc)
Adds a remaining-buffer-length parameter and check to sdpu_build_attrib_seq, and adds buffer accounting to process_service_attr_rsp, so the SDP client can no longer write an attribute ID sequence past the end of the SDP_DATA_BUF_SIZE request buffer.
Root cause
sdpu_build_attrib_seq(p_out, p_attr, num_attrs) wrote a 2- or 3-byte data element header and then num_attrs * 3 bytes (one 3-byte entry per attribute filter) into p_out without knowing how much space remained. Its callers in sdp_discovery.cc, process_service_attr_rsp and process_service_search_attr_rsp, build the request into a fixed BT_HDR buffer of SDP_DATA_BUF_SIZE allocated with osi_malloc. process_service_attr_rsp did no bytes_left accounting at all. process_service_search_attr_rsp tracked bytes_left for its base header but did not pass it to the attribute sequence builder. With a large p_ccb->p_db->num_attr_filters (plus the continuation-state bytes taken from the peer reply, *p_reply), the sequence could be written past the end of the allocated buffer.
How the fix works
- system/stack/sdp/sdpint.h and sdp_utils.cc:sdpu_build_attrib_seq: the signature gains uint16_t& bytes_left. The function now computes content_len (5 for the wildcard case, otherwise num_attrs * 3) and header_len (3 if content_len > 255, otherwise 2). If bytes_left < content_len + header_len, it hits DCHECK(0) and returns p_out unchanged without writing anything. Otherwise it subtracts header_len + content_len from bytes_left and emits the header and entries as before. content_len and header_len are int, so num_attrs * 3 cannot wrap the way the old uint16_t xx could for large num_attrs.
- sdp_discovery.cc:process_service_attr_rsp: adds uint16_t bytes_left = SDP_DATA_BUF_SIZE and computes base_bytes = sizeof(BT_HDR) + L2CAP_MIN_OFFSET + 3 (PDU header) + 2 (param len) + 4 (handle) + 2 (max attr count) + 1 + continuation length (*p_reply if p_reply is set). If base_bytes > bytes_left, it calls sdp_disconnect(p_ccb, tSDP_STATUS::SDP_INVALID_CONT_STATE), frees p_msg and returns. Otherwise it subtracts base_bytes and passes bytes_left to both sdpu_build_attrib_seq calls (filtered and wildcard).
- sdp_discovery.cc:process_service_search_attr_rsp: the existing base_bytes expression is rewritten as 3u + 2u + 2u + 1u + cont, which gives the same total as the old 3u + 2u + 3u + cont, and the comments are corrected. The bytes_left that was already there is now passed to both sdpu_build_attrib_seq calls.
- sdp_discovery.cc:sdp_snd_service_search_req: the base_bytes expression is only reformatted and commented. The total is unchanged, and this function already passed bytes_left to sdpu_build_uuid_seq.
- sdp_utils.cc: the rest of the diff (sdpu_find_ccb_by_cid, sdpu_find_ccb_by_db, sdpu_allocate_ccb, sdpu_get_active_ccb_cid, sdpu_process_pend_ccb_same_cid, sdpu_process_pend_ccb_new_cid, sdpu_clear_pend_ccb, sdpu_is_base_uuid, sdpu_get_attrib_seq_len) only moves the uint16_t xx loop variable into the for statement. No functional change.
Completeness
- Both callers of sdpu_build_attrib_seq in sdp_discovery.cc (process_service_attr_rsp and process_service_search_attr_rsp) are updated. Because the header prototype changes, any other caller fails to compile instead of silently bypassing the check.
- The check in sdpu_build_attrib_seq is a DCHECK plus an early return. In builds where DCHECK is a no-op, the function returns p_out unchanged and the callers shown in the diff do not test for failure. Request assembly therefore continues without the attribute sequence. The material does not show the send path.
- In process_service_attr_rsp, the new base_bytes check calls sdp_disconnect and frees the buffer. The equivalent failure inside sdpu_build_attrib_seq does not disconnect.
- The diff adds no upper bound on num_attr_filters where the discovery DB is populated. It only prevents the overflow at serialisation time.
Behavior changes
- process_service_attr_rsp now disconnects the SDP connection with SDP_INVALID_CONT_STATE if the fixed request header plus the continuation state cannot fit in SDP_DATA_BUF_SIZE. Before the fix there was no check.
- When the attribute filter sequence does not fit in the remaining buffer, sdpu_build_attrib_seq writes nothing (and hits the DCHECK in debug builds). The callers carry on building the ServiceAttribute or ServiceSearchAttribute request without the attribute ID list instead of overflowing the buffer.
- Internal API change: sdpu_build_attrib_seq takes an extra uint16_t& bytes_left argument.
Per major
- Android 14: Same fix: ce4be454822b in android-security-14.0.0_r28, a [RESTRICT AUTOMERGE] cherry-pick of ce1de9ef78ca. It touches sdp_discovery.cc, sdp_utils.cc and sdpint.h.
- Android 15: Same fix (59f3f429e37e in android-security-15.0.0_r16). The logic is identical. The hunks follow the 15 file layout, with process_service_search_attr_rsp before process_service_attr_rsp, and sdpu_allocate_ccb uses *p_ccb = {} on this branch.
- Android 16: Adapted fix (94566a29d020 in android-security-16.0.0_r7). The same logic is applied to reformatted code (braced if/else, tSDP_STATE:: enum, tSDP_STATUS::SDP_INVALID_CONT_STATE in both callers). It also applies the cosmetic loop change to sdpu_dump_all_ccb.
- Android 17: Same fix (5288e3e85ba9 in android-17.0.0_r1, [RESTRICT AUTOMERGE]). The sdp_discovery.cc and sdpint.h hunks and blob ids match the 14 patch. The sdp_utils.cc blob ids and line offsets differ, but the logic is the same.
Unknowns
- The material does not give the value of SDP_DATA_BUF_SIZE or the maximum num_attr_filters the discovery DB allows, so it does not establish exactly when the old code overflowed.
- The material does not say whether a remote peer alone can trigger this or whether it needs a local client that requests many attribute filters. The bulletin classifies it as RCE, High.
- The send path after sdpu_build_attrib_seq returns early is not shown, so it is unclear whether a request without the attribute list is actually transmitted.
- The meaning of the per-major figures ('5/8', or '6/8' on 16, lines of the patched region present at the latest security tags) is not explained. The resolver marks all four majors as resolved.