nimble/transport: Fix double free in ble_hci_emspi_rx_acl()#2265
Open
94xhn wants to merge 1 commit into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2260
Problem
ble_hci_emspi_rx_acl()(nimble/transport/emspi/src/ble_hci_emspi.c)allocates an mbuf, fills it with the received ACL packet, and hands it
to the host with:
ble_transport_to_hs_acl()is a thin wrapper(
nimble/transport/src/monitor.c) aroundble_transport_to_hs_acl_impl(), which in the host build resolves toble_hs_rx_data()innimble/host/src/ble_hs.c. That function isexplicitly documented to consume the mbuf regardless of outcome:
So when
ble_mqueue_put()fails (queue full / low memory),omisalready freed by the time control returns to
ble_hci_emspi_rx_acl(). Thegoto errthere then callsos_mbuf_free_chain(om)a second time on the same pointer — a doublefree of the mbuf pool block.
I checked every other caller of
ble_transport_to_hs_acl()/ble_transport_to_ll_acl()in the tree (apollo3, uart, uart_ll, usb,socket, nrf5340, dialog_cmac, hci_ipc,
nimble/controller/src/ble_ll_conn.c) — none of them freeomagainafter handing it off; they only propagate the return code (some with
a
/* we dont free the mbuf since we handed it off! */comment).ble_hci_emspiis the only transport that violates this convention.Fix
Once ownership of
omhas moved to the host viable_transport_to_hs_acl(), return its status directly instead offalling through to the shared
err:label. The earlier error paths(SPI header read failure, oversized length, SPI payload read failure)
are unaffected — those still
goto errand freeom, since ownershiphas not been transferred yet at that point.
Verification
This transport depends on the external Ambiq Apollo3 SDK
(
am_mcu_apollo.h, not part of this repo or ofmynewt-core) and hasno existing unit-test package, so it cannot be built with
newton aplain host. To verify the bug and the fix without hardware, I wrote a
standalone, host-compilable C program (reproduced in full below) that
extracts the exact control flow of
ble_hci_emspi_rx_acl()andble_hs_rx_data()verbatim and drives it against a realmalloc()/free()-backed mbuf, simulatingble_mqueue_put()failing:
masterlogic):os_mbuf_free_chain()is called twice on the same pointer; the process crashes with
STATUS_HEAP_CORRUPTION(0xC0000374) on Windows (glibc would abortwith "double free or corruption" the same way).
cleanly (status 0).
Reproduction program (compile with
-DBUGGYto see the crash, without it to see the fix)Disclosure
I want to be upfront: this fix was found and drafted with the
assistance of an AI coding agent (Claude), including the root-cause
analysis, the cross-transport comparison, and the standalone
reproduction program above. I (the human submitter) reviewed the
analysis against the current
mastersource, independently compiledand ran the reproduction on my own machine to confirm the crash/fix
behavior, and I stand behind the change. Happy to answer any
questions about it.