Skip to content

Commit 85c57c2

Browse files
jori-nordicaescolar
authored andcommitted
Bluetooth: host: ensure ownership of conn on TX path
This is a bug-fix: When upper layers want to send something, they add a `conn` object to a list. They do so by adding a node on `struct conn` rather than the object itself. We forgot to increase the reference count of the connection object when doing so. This means that there can be a scenario where the conn object is destroyed and re-used while still being on the TX list/queue. This is bad for obvious reasons. This patch fixes that by: - increasing the refcount when putting on the TX list - decreasing the refcount *only* when popping off the TX list - passing a new reference from `get_conn_ready` into `bt_conn_tx_processor` Signed-off-by: Jonathan Rico <[email protected]>
1 parent 71f668d commit 85c57c2

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

subsys/bluetooth/host/conn.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,10 @@ static bool should_stop_tx(struct bt_conn *conn)
773773
{
774774
LOG_DBG("%p", conn);
775775

776+
if (conn->state != BT_CONN_CONNECTED) {
777+
return true;
778+
}
779+
776780
/* TODO: This function should be overridable by the application: they
777781
* should be able to provide their own heuristic.
778782
*/
@@ -804,6 +808,12 @@ void bt_conn_data_ready(struct bt_conn *conn)
804808

805809
/* The TX processor will call the `pull_cb` to get the buf */
806810
if (!atomic_set(&conn->_conn_ready_lock, 1)) {
811+
/* Attach a reference to the `bt_dev.le.conn_ready` list.
812+
*
813+
* This reference will be consumed when the conn is popped off
814+
* the list (in `get_conn_ready`).
815+
*/
816+
bt_conn_ref(conn);
807817
sys_slist_append(&bt_dev.le.conn_ready,
808818
&conn->_conn_ready);
809819
LOG_DBG("raised");
@@ -857,6 +867,12 @@ struct bt_conn *get_conn_ready(void)
857867
return NULL;
858868
}
859869

870+
/* `conn` borrows from the list node. That node is _not_ popped yet.
871+
*
872+
* If we end up not popping that conn off the list, we have to make sure
873+
* to increase the refcount before returning a pointer to that
874+
* connection out of this function.
875+
*/
860876
struct bt_conn *conn = CONTAINER_OF(node, struct bt_conn, _conn_ready);
861877

862878
if (dont_have_viewbufs()) {
@@ -887,6 +903,7 @@ struct bt_conn *get_conn_ready(void)
887903
}
888904

889905
if (should_stop_tx(conn)) {
906+
/* Move reference off the list and into the `conn` variable. */
890907
__maybe_unused sys_snode_t *s = sys_slist_get(&bt_dev.le.conn_ready);
891908

892909
__ASSERT_NO_MSG(s == node);
@@ -902,9 +919,11 @@ struct bt_conn *get_conn_ready(void)
902919
LOG_DBG("appending %p to back of TX queue", conn);
903920
bt_conn_data_ready(conn);
904921
}
922+
923+
return conn;
905924
}
906925

907-
return conn;
926+
return bt_conn_ref(conn);
908927
}
909928

910929
/* Crazy that this file is compiled even if this is not true, but here we are. */
@@ -985,7 +1004,8 @@ void bt_conn_tx_processor(void)
9851004
LOG_DBG("processing conn %p", conn);
9861005

9871006
if (conn->state != BT_CONN_CONNECTED) {
988-
LOG_ERR("conn %p: not connected", conn);
1007+
LOG_WRN("conn %p: not connected", conn);
1008+
9891009
/* Call the user callbacks & destroy (final-unref) the buffers
9901010
* we were supposed to send.
9911011
*/
@@ -994,7 +1014,8 @@ void bt_conn_tx_processor(void)
9941014
destroy_and_callback(conn, buf, cb, ud);
9951015
buf = conn->tx_data_pull(conn, SIZE_MAX, &buf_len);
9961016
}
997-
return;
1017+
1018+
goto exit;
9981019
}
9991020

10001021
/* now that we are guaranteed resources, we can pull data from the upper
@@ -1008,7 +1029,8 @@ void bt_conn_tx_processor(void)
10081029
* the upper layer when it has more data.
10091030
*/
10101031
LOG_DBG("no buf returned");
1011-
return;
1032+
1033+
goto exit;
10121034
}
10131035

10141036
bool last_buf = conn_mtu(conn) >= buf_len;
@@ -1044,13 +1066,17 @@ void bt_conn_tx_processor(void)
10441066
destroy_and_callback(conn, buf, cb, ud);
10451067
bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
10461068

1047-
return;
1069+
goto exit;
10481070
}
10491071

10501072
/* Always kick the TX work. It will self-suspend if it doesn't get
10511073
* resources or there is nothing left to send.
10521074
*/
10531075
bt_tx_irq_raise();
1076+
1077+
exit:
1078+
/* Give back the ref that `get_conn_ready()` gave us */
1079+
bt_conn_unref(conn);
10541080
}
10551081

10561082
static void process_unack_tx(struct bt_conn *conn)

subsys/bluetooth/host/conn_internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,5 +547,11 @@ void bt_conn_tx_processor(void);
547547

548548
/* To be called by upper layers when they want to send something.
549549
* Functions just like an IRQ.
550+
*
551+
* Note: This fn will take and hold a reference to `conn` until the IRQ for that
552+
* conn is serviced.
553+
* For the current implementation, that means:
554+
* - ref the conn when putting on an "conn-ready" slist
555+
* - unref the conn when popping the conn from the slist
550556
*/
551557
void bt_conn_data_ready(struct bt_conn *conn);

subsys/bluetooth/host/hci_core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ struct bt_dev_le {
311311
#endif /* CONFIG_BT_SMP */
312312
/* List of `struct bt_conn` that have either pending data to send, or
313313
* something to process (e.g. a disconnection event).
314+
*
315+
* Each element in this list contains a reference to its `conn` object.
314316
*/
315317
sys_slist_t conn_ready;
316318
};

0 commit comments

Comments
 (0)