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
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
For more information
If you have any questions or comments about this advisory:
embargo: 2026-07-31
The Zephyr Bluetooth GATT client CCC-write response handler
gatt_write_ccc_rsp()insubsys/bluetooth/host/gatt.cinvoked the application'sparams->subscribe()callback after it had already calledparams->notify(conn, params, NULL, 0).Per the public GATT API, a notify callback with
NULLdata is the documented signal that the subscription has terminated and thebt_gatt_subscribe_paramsstruct may be freed or reused by the application; callingsubscribe()on the struct afterwards is a use-after-free, including an indirect call through the freedparams->subscribefunction 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 throughatt_error_rsp->att_handle_rspintogatt_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 terminatingnotify(NULL)in both the error and unsubscribe paths.Affected components
subsys/bluetooth/hostAffected versions
>= 2.4.0, <= 4.4.1Fix
Fixed (merged) in c7292f2
Projected fixed version: 4.5.0 (the fix is merged on
mainbut 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
Patches
mainv4.4-branchv4.3-branchv3.7-branchFor more information
If you have any questions or comments about this advisory:
embargo: 2026-07-31