]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
Bluetooth: mgmt: fix UAF in pair command cancellation
authorZihan Xi <xizh2024@lzu.edu.cn>
Tue, 21 Jul 2026 14:36:07 +0000 (22:36 +0800)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Tue, 28 Jul 2026 18:49:20 +0000 (14:49 -0400)
The pairing completion and authentication failure callbacks look up the
pending MGMT_OP_PAIR_DEVICE command by walking hdev->mgmt_pending. The
lookup returned a command that was still linked on the shared pending list,
without keeping mgmt_pending_lock held for the later dereference and
removal.

A concurrent MGMT_OP_CANCEL_PAIR_DEVICE request can remove and free the
same pending command before the callback uses it. The reverse race is also
possible when cancel_pair_device() gets a command from pending_find() and a
callback removes it before the cancel path dereferences it. This can lead
to a use-after-free and a second list_del().

Make the pairing lookup helpers transfer ownership of the pending command
by removing it from hdev->mgmt_pending while holding mgmt_pending_lock.
The callbacks and cancel path then complete the command and free it
directly, so racing paths cannot find or free the same command again. Take
a temporary hci_conn reference in cancel_pair_device() because the command
completion drops the reference stored in the pending command.

Fixes: e9a416b5ce0c ("Bluetooth: Add mgmt_pair_device command")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <xizh2024@lzu.edu.cn>
Reviewed-by: Ren Wei <enjou1224z@gmail.com>
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
net/bluetooth/mgmt.c

index 1db10e0f617f92cdde51cf8cd5f618843a21e724..4fd37ac799863bc387085c9805bbd468cdee5fa7 100644 (file)
@@ -3514,11 +3514,13 @@ static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data,
                                 NULL, 0);
 }
 
-static struct mgmt_pending_cmd *find_pairing(struct hci_conn *conn)
+static struct mgmt_pending_cmd *remove_pairing(struct hci_conn *conn)
 {
        struct hci_dev *hdev = conn->hdev;
        struct mgmt_pending_cmd *cmd;
 
+       mutex_lock(&hdev->mgmt_pending_lock);
+
        list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
                if (cmd->opcode != MGMT_OP_PAIR_DEVICE)
                        continue;
@@ -3526,9 +3528,39 @@ static struct mgmt_pending_cmd *find_pairing(struct hci_conn *conn)
                if (cmd->user_data != conn)
                        continue;
 
+               list_del(&cmd->list);
+               mutex_unlock(&hdev->mgmt_pending_lock);
                return cmd;
        }
 
+       mutex_unlock(&hdev->mgmt_pending_lock);
+
+       return NULL;
+}
+
+static struct mgmt_pending_cmd *remove_pairing_by_addr(struct hci_dev *hdev,
+                                                      bdaddr_t *bdaddr)
+{
+       struct mgmt_pending_cmd *cmd;
+       struct hci_conn *conn;
+
+       mutex_lock(&hdev->mgmt_pending_lock);
+
+       list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
+               if (cmd->opcode != MGMT_OP_PAIR_DEVICE)
+                       continue;
+
+               conn = cmd->user_data;
+               if (bacmp(bdaddr, &conn->dst) != 0)
+                       continue;
+
+               list_del(&cmd->list);
+               mutex_unlock(&hdev->mgmt_pending_lock);
+               return cmd;
+       }
+
+       mutex_unlock(&hdev->mgmt_pending_lock);
+
        return NULL;
 }
 
@@ -3566,10 +3598,10 @@ void mgmt_smp_complete(struct hci_conn *conn, bool complete)
        u8 status = complete ? MGMT_STATUS_SUCCESS : MGMT_STATUS_FAILED;
        struct mgmt_pending_cmd *cmd;
 
-       cmd = find_pairing(conn);
+       cmd = remove_pairing(conn);
        if (cmd) {
                cmd->cmd_complete(cmd, status);
-               mgmt_pending_remove(cmd);
+               mgmt_pending_free(cmd);
        }
 }
 
@@ -3579,14 +3611,14 @@ static void pairing_complete_cb(struct hci_conn *conn, u8 status)
 
        BT_DBG("status %u", status);
 
-       cmd = find_pairing(conn);
+       cmd = remove_pairing(conn);
        if (!cmd) {
                BT_DBG("Unable to find a pending command");
                return;
        }
 
        cmd->cmd_complete(cmd, mgmt_status(status));
-       mgmt_pending_remove(cmd);
+       mgmt_pending_free(cmd);
 }
 
 static void le_pairing_complete_cb(struct hci_conn *conn, u8 status)
@@ -3598,14 +3630,14 @@ static void le_pairing_complete_cb(struct hci_conn *conn, u8 status)
        if (!status)
                return;
 
-       cmd = find_pairing(conn);
+       cmd = remove_pairing(conn);
        if (!cmd) {
                BT_DBG("Unable to find a pending command");
                return;
        }
 
        cmd->cmd_complete(cmd, mgmt_status(status));
-       mgmt_pending_remove(cmd);
+       mgmt_pending_free(cmd);
 }
 
 static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
@@ -3762,23 +3794,17 @@ static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
                goto unlock;
        }
 
-       cmd = pending_find(MGMT_OP_PAIR_DEVICE, hdev);
+       cmd = remove_pairing_by_addr(hdev, &addr->bdaddr);
        if (!cmd) {
                err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE,
                                      MGMT_STATUS_INVALID_PARAMS);
                goto unlock;
        }
 
-       conn = cmd->user_data;
-
-       if (bacmp(&addr->bdaddr, &conn->dst) != 0) {
-               err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE,
-                                     MGMT_STATUS_INVALID_PARAMS);
-               goto unlock;
-       }
+       conn = hci_conn_get(cmd->user_data);
 
        cmd->cmd_complete(cmd, MGMT_STATUS_CANCELLED);
-       mgmt_pending_remove(cmd);
+       mgmt_pending_free(cmd);
 
        err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, 0,
                                addr, sizeof(*addr));
@@ -3796,6 +3822,8 @@ static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
        if (conn->conn_reason == CONN_REASON_PAIR_DEVICE)
                hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM);
 
+       hci_conn_put(conn);
+
 unlock:
        hci_dev_unlock(hdev);
        return err;
@@ -10137,14 +10165,14 @@ void mgmt_auth_failed(struct hci_conn *conn, u8 hci_status)
        ev.addr.type = link_to_bdaddr(conn->type, conn->dst_type);
        ev.status = status;
 
-       cmd = find_pairing(conn);
+       cmd = remove_pairing(conn);
 
        mgmt_event(MGMT_EV_AUTH_FAILED, conn->hdev, &ev, sizeof(ev),
                    cmd ? cmd->sk : NULL);
 
        if (cmd) {
                cmd->cmd_complete(cmd, status);
-               mgmt_pending_remove(cmd);
+               mgmt_pending_free(cmd);
        }
 }