From 330963214debc611b8f09688c7e4c22cde5d7178 Mon Sep 17 00:00:00 2001 From: 94xhn <87560781+94xhn@users.noreply.github.com> Date: Sat, 11 Jul 2026 23:46:06 +0800 Subject: [PATCH] nimble/transport: Fix double free in ble_hci_emspi_rx_acl() ble_hci_emspi_rx_acl() calls ble_transport_to_hs_acl(om) once the ACL packet has been fully received, and jumps to the shared `err` label (which calls os_mbuf_free_chain(om)) whenever that call returns a non-zero status. ble_transport_to_hs_acl() forwards to ble_hs_rx_data(), which is documented to "consume the supplied mbuf, regardless of the outcome": on failure (e.g. ble_mqueue_put() hitting capacity) it already frees `om` via os_mbuf_free_chain() before returning an error. The `goto err` in ble_hci_emspi_rx_acl() then frees the same `om` a second time, corrupting the mbuf pool under low-memory or queue-full conditions. Every other transport that calls ble_transport_to_hs_acl() / ble_transport_to_ll_acl() (apollo3, uart, uart_ll, usb, socket, nrf5340, dialog_cmac, hci_ipc, controller/ble_ll_conn.c) already follows the "ownership transferred, do not free again" rule and simply propagates the return code. Make ble_hci_emspi_rx_acl() do the same by returning directly instead of falling through to the `err` path once ownership has moved to the host. Verified the double free with a standalone host-side program that reproduces the exact control flow of ble_hci_emspi_rx_acl() and ble_hs_rx_data() against a real malloc()/free()-backed mbuf: the current code calls free() twice on the same pointer and the process aborts with STATUS_HEAP_CORRUPTION (0xC0000374); with this change applied only a single free() occurs and the process exits cleanly. Disclosure: this fix, its analysis and the accompanying reproduction program were produced with the assistance of an AI coding agent (Claude), reviewed and submitted by the human author. Signed-off-by: 94xhn <87560781+94xhn@users.noreply.github.com> --- nimble/transport/emspi/src/ble_hci_emspi.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nimble/transport/emspi/src/ble_hci_emspi.c b/nimble/transport/emspi/src/ble_hci_emspi.c index d07820f7bb..2466b3bd2a 100644 --- a/nimble/transport/emspi/src/ble_hci_emspi.c +++ b/nimble/transport/emspi/src/ble_hci_emspi.c @@ -479,12 +479,13 @@ ble_hci_emspi_rx_acl(void) OS_MBUF_PKTLEN(om) = BLE_HCI_DATA_HDR_SZ + len; om->om_len = BLE_HCI_DATA_HDR_SZ + len; - rc = ble_transport_to_hs_acl(om); - if (rc != 0) { - goto err; - } - - return 0; + /* + * Ownership of `om` is transferred to the host here, regardless of the + * return value (see the comment on ble_hs_rx_data() in + * nimble/host/src/ble_hs.c). On failure the host has already freed it, + * so it must not be freed again via the `err` path below. + */ + return ble_transport_to_hs_acl(om); err: os_mbuf_free_chain(om);