Skip to content

Use-after-free of GATT subscribe params in Bluetooth host CCC-write response handler

High
d3zd3z published GHSA-29xh-jm2m-4qvx Jul 31, 2026

Software

zephyr

Affected versions

>= 2.4.0, <= 4.4.1

Patched versions

4.5.0

Description

The Zephyr Bluetooth GATT client CCC-write response handler gatt_write_ccc_rsp() in subsys/bluetooth/host/gatt.c invoked the application's params->subscribe() callback after it had already called params->notify(conn, params, NULL, 0).

Per the public GATT API, a notify callback with NULL data is the documented signal that the subscription has terminated and the bt_gatt_subscribe_params struct may be freed or reused by the application; calling subscribe() on the struct afterwards is a use-after-free, including an indirect call through the freed params->subscribe function pointer.

The error branch is remotely (adjacent) reachable: a Zephyr device acting as a GATT client that calls bt_gatt_subscribe() can be driven into this ordering when a connected GATT server peer answers the CCC write with an ATT Error Response (the peer-supplied error code flows through att_error_rsp -> att_handle_rsp into gatt_write_ccc_rsp).

For applications that free or recycle subscription parameters in their notification-termination handler, this results in memory corruption, a crash (denial of service), or potentially attacker-influenced control flow. The fix reorders the handler so the subscribe() callback runs before the terminating notify(NULL) in both the error and unsubscribe paths.

Affected components

  • subsys/bluetooth/host

Affected versions

>= 2.4.0, <= 4.4.1

Fix

Fixed (merged) in c7292f2

Projected fixed version: 4.5.0 (the fix is merged on main but not yet released; this forecast should be confirmed against the actual release).

Introduced by: Long-standing in gatt_write_ccc_rsp ordering (subscribe callback invoked after gatt_sub_remove/notify(NULL)); present at least since the CCC write-queuing logic, predates v4.4.0

Evidence

  • subsys/bluetooth/host/gatt.c:5290-5299 (parent) — subscribe() called after gatt_sub_remove()/notify(NULL); fix moves subscribe() before the release
  • subsys/bluetooth/host/gatt.c:3481-3486 — gatt_sub_remove() unconditionally calls params->notify(conn, params, NULL, 0), the release point
  • include/zephyr/bluetooth/gatt.h:2157,2330-2331 — notify NULL data == subscription removed; 'until then the parameters cannot be reused'
  • subsys/bluetooth/host/att.c:2626 — att_error_rsp sets err = rsp->error directly from the remote ATT_ERROR_RSP PDU
  • subsys/bluetooth/host/att.c:1044 — att_handle_rsp calls func(conn, err, ...) = gatt_write_ccc_rsp with the peer-supplied err
  • subsys/bluetooth/host/gatt.c:5452,5541 — bt_gatt_subscribe/unsubscribe register gatt_write_ccc_rsp as the CCC write response callback

Patches

Branch Pull request Status
main #99920 merged
v4.4-branch #111430 merged
v4.3-branch #111429 merged
v3.7-branch #111428 merged

For more information

If you have any questions or comments about this advisory:

embargo: 2026-07-31

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Adjacent
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H

CVE ID

CVE-2026-10685

Weaknesses

Use After Free

The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory belongs to the code that operates on the new pointer. Learn more on MITRE.