Double free in `ble_hci_emspi_rx_acl()` on host ACL delivery failure

Open Beginner friendly
#2,260 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
c
Domain
embedded-iot

Research direction

Start in nimble/transport/emspi/src/ble_hci_emspi.c at ble_hci_emspi_rx_acl(), then read ble_transport_to_hs_acl_impl() in nimble/host/src/ble_hs.c and the ownership comment in ble_hs_rx_data(). Trace the ble_mqueue_put() failure path and verify that a host delivery failure consumes the mbuf only once, while earlier receive failures retain their existing cleanup.

Written by the indexing model from the issue text.

Description

I found a possible double free in the EMSPI HCI transport receive path when an incoming ACL packet is passed to the host and host-side queuing fails.

File: nimble/transport/emspi/src/ble_hci_emspi.c

Function: ble_hci_emspi_rx_acl

Relevant code:

om = ble_transport_alloc_acl_from_ll();
assert(om != NULL);

rc = ble_hci_emspi_rx(om->om_data, BLE_HCI_DATA_HDR_SZ);
if (rc != 0) {
    goto err;
}

...

rc = ble_transport_to_hs_acl(om);
if (rc != 0) {
    goto err;
}

return 0;

err:
    os_mbuf_free_chain(om);
    return rc;

The error label is correct for failures that occur before ownership is passed
to the host. However, ble_transport_to_hs_acl() eventually calls the host
implementation:

File: nimble/host/src/ble_hs.c

ble_transport_to_hs_acl_impl(struct os_mbuf *om)
{
    return ble_hs_rx_data(om, NULL);
}

ble_hs_rx_data() documents and implements that it consumes the mbuf regardless
of the outcome:

/* Called when a data packet is received from the controller.  This function
 * consumes the supplied mbuf, regardless of the outcome.
 */
static int
ble_hs_rx_data(struct os_mbuf *om, void *arg)
{
    ...
    rc = ble_mqueue_put(&ble_hs_rx_q, ble_hs_evq, om);
    if (rc != 0) {
        os_mbuf_free_chain(om);
        return BLE_HS_EOS;
    }

    return 0;
}

So when ble_mqueue_put() fails, ble_hs_rx_data() already frees om and
returns an error. ble_hci_emspi_rx_acl() then sees the non-zero return value,
jumps to err, and frees the same mbuf again.

This can happen under low-memory or queue-allocation failure conditions in the
host receive path.

Suggested fix: after calling ble_transport_to_hs_acl(om), do not free om on
failure because ownership has already been transferred. One option is to return
the error directly:

rc = ble_transport_to_hs_acl(om);
if (rc != 0) {
    return rc;
}
Dominant language
C
Stars
892
Forks
512
Avg merge
6d 1h
Merged PRs (30d)
4

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from apache/mynewt-nimble

All issues in apache/mynewt-nimble

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.