[PATCH 3/3] Bluetooth: MGMT: reference-count struct mgmt_mesh_tx
Baul Lee <[email protected]>
| Newsgroups | org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
hci_cmd_sync_submit() stores the caller's pointer in a work entry and takes no reference to what it names. hci_cmd_sync_work() drains that list from the head on an ordered workqueue, so an entry queued earlier runs to completion before a later one is looked at. mesh_send() links a struct mgmt_mesh_tx onto hdev->mesh_pending and queues mesh_send_sync() with the raw pointer. A MESH_SEND_CANCEL issued before that send leaves its send_cancel() entry ahead in the queue, so send_cancel() runs first: it picks the object out of hdev->mesh_pending with mgmt_mesh_next(), which returns it without unlinking it, and frees it through mesh_send_complete(). Nothing invalidates the pointer the later entry still holds, so mesh_send_sync() and its destroy callback mesh_send_start_complete() run on freed memory: [ 43.618774] BUG: KASAN: slab-use-after-free in mesh_send_sync+0xec/0x1b4 [ 43.618851] Write of size 1 at addr ffff000009f2a6a9 by task kworker/u5:2/148 [ 43.619464] mesh_send_sync+0xec/0x1b4 [ 43.619531] hci_cmd_sync_work+0xac/0x128 [ 43.620882] Freed by task 148: [ 43.621171] mgmt_mesh_remove+0x94/0x110 [ 43.621218] send_cancel+0xd8/0x1cc [ 43.621270] hci_cmd_sync_work+0xac/0x128 Offset 41 is mesh_tx->instance, the only byte mesh_send_sync() stores through mesh_tx; it reads further fields of the same freed object, and hci_set_adv_instance_data() copies 31 of those bytes into a live struct adv_info. The free and the use are consecutive iterations of one hci_cmd_sync_work() loop on one kworker, so queue order alone decides it. Give struct mgmt_mesh_tx a reference count. hdev->mesh_pending holds one, and every pointer handed to hci_cmd_sync_queue() takes a second one that the destroy callback drops, so a cancel that unlinks the object while a work entry is still queued no longer releases it. mgmt_mesh_remove() becomes an unlink plus a put and returns early when the object is already unlinked, because it can now outlive its removal from the list. The list reference is still dropped under hci_dev_lock() only, so mesh_send()'s use of mesh_tx->handle after queueing stays covered by the lock it holds. A cancel that arrives before the queued mesh_send_sync() now lets that send run rather than freeing the object under it; suppressing the transmission as well is a separate change. hci_cmd_sync_dequeue() is the other in-tree option, but mgmt_mesh_remove() is not given the hci_dev it needs and cannot tell whether the work entry has already been taken off cmd_sync_work_list. Discovered by XBOW, triaged by Baul Lee <[email protected]> Fixes: b338d91703fa ("Bluetooth: Implement support for Mesh") Cc: [email protected] Signed-off-by: Baul Lee <[email protected]> --- net/bluetooth/mgmt.c | 25 +++++++++++++++++-------- net/bluetooth/mgmt_util.c | 23 +++++++++++++++++++++-- net/bluetooth/mgmt_util.h | 3 +++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 583e05ec4377..c562f8c95294 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1127,13 +1127,15 @@ static void mesh_next(struct hci_dev *hdev, void *data, int err) return; } - err = hci_cmd_sync_queue(hdev, mesh_send_sync, mesh_tx, + err = hci_cmd_sync_queue(hdev, mesh_send_sync, mgmt_mesh_get(mesh_tx), mesh_send_start_complete); - if (err < 0) + if (err < 0) { + mgmt_mesh_put(mesh_tx); mesh_send_complete(hdev, mesh_tx, false); - else + } else { hci_dev_set_flag(hdev, HCI_MESH_SENDING); + } hci_dev_unlock(hdev); } @@ -2322,7 +2324,7 @@ static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err) u8 mgmt_err = mgmt_status(err); if (err == -ECANCELED) - return; + goto put; /* Report any errors here, but don't report completion */ @@ -2332,12 +2334,15 @@ static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err) hci_dev_lock(hdev); mesh_send_complete(hdev, mesh_tx, false); hci_dev_unlock(hdev); - return; + goto put; } mesh_send_interval = msecs_to_jiffies((send->cnt) * 25); queue_delayed_work(hdev->req_workqueue, &hdev->mesh_send_done, mesh_send_interval); + +put: + mgmt_mesh_put(mesh_tx); } static int mesh_send_sync(struct hci_dev *hdev, void *data) @@ -2538,11 +2543,15 @@ static int mesh_send(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) sending = hci_dev_test_flag(hdev, HCI_MESH_SENDING); mesh_tx = mgmt_mesh_add(sk, hdev, send, len); - if (!mesh_tx) + if (!mesh_tx) { err = -ENOMEM; - else if (!sending) - err = hci_cmd_sync_queue(hdev, mesh_send_sync, mesh_tx, + } else if (!sending) { + err = hci_cmd_sync_queue(hdev, mesh_send_sync, + mgmt_mesh_get(mesh_tx), mesh_send_start_complete); + if (err < 0) + mgmt_mesh_put(mesh_tx); + } if (err < 0) { bt_dev_err(hdev, "Send Mesh Failed %d", err); diff --git a/net/bluetooth/mgmt_util.c b/net/bluetooth/mgmt_util.c index a822091f2907..c7543964525a 100644 --- a/net/bluetooth/mgmt_util.c +++ b/net/bluetooth/mgmt_util.c @@ -422,6 +422,7 @@ struct mgmt_mesh_tx *mgmt_mesh_add(struct sock *sk, struct hci_dev *hdev, if (!mesh_tx) return NULL; + refcount_set(&mesh_tx->ref, 1); hdev->mesh_send_ref++; if (!hdev->mesh_send_ref) hdev->mesh_send_ref++; @@ -438,9 +439,27 @@ struct mgmt_mesh_tx *mgmt_mesh_add(struct sock *sk, struct hci_dev *hdev, return mesh_tx; } -void mgmt_mesh_remove(struct mgmt_mesh_tx *mesh_tx) +struct mgmt_mesh_tx *mgmt_mesh_get(struct mgmt_mesh_tx *mesh_tx) +{ + refcount_inc(&mesh_tx->ref); + + return mesh_tx; +} + +void mgmt_mesh_put(struct mgmt_mesh_tx *mesh_tx) { - list_del(&mesh_tx->list); + if (!refcount_dec_and_test(&mesh_tx->ref)) + return; + sock_put(mesh_tx->sk); kfree(mesh_tx); } + +void mgmt_mesh_remove(struct mgmt_mesh_tx *mesh_tx) +{ + if (list_empty(&mesh_tx->list)) + return; + + list_del_init(&mesh_tx->list); + mgmt_mesh_put(mesh_tx); +} diff --git a/net/bluetooth/mgmt_util.h b/net/bluetooth/mgmt_util.h index 20810cf06e81..b38970c1332c 100644 --- a/net/bluetooth/mgmt_util.h +++ b/net/bluetooth/mgmt_util.h @@ -19,6 +19,7 @@ struct mgmt_mesh_tx { struct list_head list; + refcount_t ref; int index; size_t param_len; struct sock *sk; @@ -72,4 +73,6 @@ struct mgmt_mesh_tx *mgmt_mesh_find(struct hci_dev *hdev, u8 handle); struct mgmt_mesh_tx *mgmt_mesh_next(struct hci_dev *hdev, struct sock *sk); struct mgmt_mesh_tx *mgmt_mesh_add(struct sock *sk, struct hci_dev *hdev, void *data, u16 len); +struct mgmt_mesh_tx *mgmt_mesh_get(struct mgmt_mesh_tx *mesh_tx); +void mgmt_mesh_put(struct mgmt_mesh_tx *mesh_tx); void mgmt_mesh_remove(struct mgmt_mesh_tx *mesh_tx); -- 2.50.1 (Apple Git-155)