mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Bluetooth: mgmt: fix UAF in pair command cancellation
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: e9a416b5ce ("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>
This commit is contained in:
committed by
Luiz Augusto von Dentz
parent
d57e506f6a
commit
d0a7b48ad0
+46
-18
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user