Skip to content

Commit

Permalink
Bluetooth: Fix hci_conn reference counting for auto-connections
Browse files Browse the repository at this point in the history
Recently the LE passive scanning and auto-connections feature was
introduced. It uses the hci_connect_le() API which returns a hci_conn
along with a reference count to that object. All previous users would
tie this returned reference to some existing object, such as an L2CAP
channel, and there'd be no leaked references this way. For
auto-connections however the reference was returned but not stored
anywhere, leaving established connections with one higher reference
count than they should have.

Instead of playing special tricks with hci_conn_hold/drop this patch
associates the returned reference from hci_connect_le() with the object
that in practice does own this reference, i.e. the hci_conn_params
struct that caused us to initiate a connection in the first place. Once
the connection is established or fails to establish this reference is
removed appropriately.

One extra thing needed is to call hci_pend_le_actions_clear() before
calling hci_conn_hash_flush() so that the reference is cleared before
the hci_conn objects are fully removed.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
  • Loading branch information
Johan Hedberg committed Aug 20, 2014
1 parent 6697dab commit f161dd4
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 4 deletions.
2 changes: 2 additions & 0 deletions include/net/bluetooth/hci_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ struct hci_conn_params {
HCI_AUTO_CONN_ALWAYS,
HCI_AUTO_CONN_LINK_LOSS,
} auto_connect;

struct hci_conn *conn;
};

extern struct list_head hci_dev_list;
Expand Down
8 changes: 8 additions & 0 deletions net/bluetooth/hci_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,14 @@ EXPORT_SYMBOL(hci_get_route);
void hci_le_conn_failed(struct hci_conn *conn, u8 status)
{
struct hci_dev *hdev = conn->hdev;
struct hci_conn_params *params;

params = hci_pend_le_action_lookup(&hdev->pend_le_conns, &conn->dst,
conn->dst_type);
if (params && params->conn) {
hci_conn_drop(params->conn);
params->conn = NULL;
}

conn->state = BT_CLOSED;

Expand Down
14 changes: 12 additions & 2 deletions net/bluetooth/hci_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -2536,8 +2536,13 @@ static void hci_pend_le_actions_clear(struct hci_dev *hdev)
{
struct hci_conn_params *p;

list_for_each_entry(p, &hdev->le_conn_params, list)
list_for_each_entry(p, &hdev->le_conn_params, list) {
if (p->conn) {
hci_conn_drop(p->conn);
p->conn = NULL;
}
list_del_init(&p->action);
}

BT_DBG("All LE pending actions cleared");
}
Expand Down Expand Up @@ -2578,8 +2583,8 @@ static int hci_dev_do_close(struct hci_dev *hdev)

hci_dev_lock(hdev);
hci_inquiry_cache_flush(hdev);
hci_conn_hash_flush(hdev);
hci_pend_le_actions_clear(hdev);
hci_conn_hash_flush(hdev);
hci_dev_unlock(hdev);

hci_notify(hdev, HCI_DEV_DOWN);
Expand Down Expand Up @@ -3727,6 +3732,9 @@ void hci_conn_params_del(struct hci_dev *hdev, bdaddr_t *addr, u8 addr_type)
if (!params)
return;

if (params->conn)
hci_conn_drop(params->conn);

list_del(&params->action);
list_del(&params->list);
kfree(params);
Expand Down Expand Up @@ -3757,6 +3765,8 @@ void hci_conn_params_clear_all(struct hci_dev *hdev)
struct hci_conn_params *params, *tmp;

list_for_each_entry_safe(params, tmp, &hdev->le_conn_params, list) {
if (params->conn)
hci_conn_drop(params->conn);
list_del(&params->action);
list_del(&params->list);
kfree(params);
Expand Down
17 changes: 15 additions & 2 deletions net/bluetooth/hci_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -4221,8 +4221,13 @@ static void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
hci_proto_connect_cfm(conn, ev->status);

params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type);
if (params)
if (params) {
list_del_init(&params->action);
if (params->conn) {
hci_conn_drop(params->conn);
params->conn = NULL;
}
}

unlock:
hci_update_background_scan(hdev);
Expand Down Expand Up @@ -4304,8 +4309,16 @@ static void check_pending_le_conn(struct hci_dev *hdev, bdaddr_t *addr,

conn = hci_connect_le(hdev, addr, addr_type, BT_SECURITY_LOW,
HCI_LE_AUTOCONN_TIMEOUT, HCI_ROLE_MASTER);
if (!IS_ERR(conn))
if (!IS_ERR(conn)) {
/* Store the pointer since we don't really have any
* other owner of the object besides the params that
* triggered it. This way we can abort the connection if
* the parameters get removed and keep the reference
* count consistent once the connection is established.
*/
params->conn = conn;
return;
}

switch (PTR_ERR(conn)) {
case -EBUSY:
Expand Down

0 comments on commit f161dd4

Please sign in to comment.