CVE-2023-40087 AI-generated analysis
platform/packages/modules/Bluetooth, system/btif AVRCP audio track (btif_avrcp_audio_track.cc)
Bounds the sample loops in transcodeQ15ToFloat, transcodeQ23ToFloat and transcodeQ31ToFloat (system/btif/src/btif_avrcp_audio_track.cc) by the destination buffer length (trackHolder->bufferLength) and replaces the off-by-one `<=` loop condition, which ran one iteration past length/sampleSize.
Root cause
The three transcode helpers in system/btif/src/btif_avrcp_audio_track.cc looped with `for (; i <= length / sampleSize; i++)` and wrote `trackHolder->buffer[i]`. This had two problems. First, the `<=` condition ran one extra iteration: it wrote index length/sampleSize and read one sample beyond the length-derived range of the input `buffer`. Second, the loop bound depended only on the input `length` and never on `trackHolder->bufferLength`. A `length` larger than the destination float buffer could therefore cause an out-of-bounds write into `trackHolder->buffer`. Per index search, transcodeToPcmFloat selects these helpers by switching on trackHolder->bitsPerSample (16, 24 or 32). The unit test reaches them through BtifAvrcpAudioTrackWriteData.
How the fix works
- system/btif/src/btif_avrcp_audio_track.cc:transcodeQ15ToFloat: the loop condition changed from `i <= length / sampleSize` to `i < std::min(trackHolder->bufferLength, length / sampleSize)`. This removes the extra iteration and caps the samples written at the destination buffer length.
- system/btif/src/btif_avrcp_audio_track.cc:transcodeQ23ToFloat: same loop bound change. The per-sample 24-bit extraction in the loop body is unchanged.
- system/btif/src/btif_avrcp_audio_track.cc:transcodeQ31ToFloat: same loop bound change.
- The return value `i * sampleSize` now reflects the clamped number of samples that were transcoded.
- system/btif/test/btif_avrcp_audio_track_test.cc:setMaxAudioTrackGain_minGain_bufferStreamDucked: the test now writes `bufferLength` bytes instead of `bufferLength - 1`, to match the new `<` semantics.
Completeness
- All three bit-depth paths (16, 24, 32) that transcodeToPcmFloat selects get the same bound. None keeps the old condition.
- The destination write `trackHolder->buffer[i]` is now bounded by both the input length and `trackHolder->bufferLength`.
- transcodeQ23ToFloat still reads `*((int32_t*)(buffer + offset - 1))`. When i == 0, this read starts one byte before `buffer`. The fix does not touch this expression (see potential_variants).
- The fix relies on `trackHolder->bufferLength` matching the actual allocation of `trackHolder->buffer`. The material does not show the allocation site.
- If `length` is not a multiple of sampleSize, the trailing partial sample is now skipped instead of producing an extra iteration.
Behavior changes
- Writes larger than the audio track buffer are now truncated to `bufferLength` samples, and the return value reports the reduced byte count.
- Each call transcodes one fewer sample than the old `<=` loop did. The unit test was adjusted to match.
Per major
- Android 14: Same fix, in android-security-14.0.0_r3. It came through the automerger chain from a tm-dev change (Change-Id I0389f56ae210b4976821e0ceeb21a7a0c2965a62, cherry-picked from 3d7c7f4c2c51), and the commit is linked by subject only. At android-security-14.0.0_r29 the `std::min(trackHolder->bufferLength, length / sampleSize)` loops are present in all three helpers. The facts report 6/8 patched-region lines present.
- Android 15: Not affected per the bulletin. The fixed loop bounds are present at android-security-15.0.0_r17 (6/8 patched-region lines present).
- Android 16: Not affected per the bulletin. The fixed code is present at android-security-16.0.0_r8 (6/8 patched-region lines present).
- Android 17: Not affected per the bulletin. The fixed code is present at android-security-17.0.0_r1 (6/8 patched-region lines present).
Unknowns
- Where `length` originates, i.e. which decoder or transport path feeds BtifAvrcpAudioTrackWriteData, and whether a remote peer can influence it.
- How BtifAvrcpAudioTrackCreate allocates `trackHolder->buffer` and sets `bufferLength`.
- Why the bulletin lists 14-next as affected while 15 is not listed.
- Which 2 of the 8 patched-region lines the per-major facts count as absent.