Bluetooth: mgmt: fix pending command UAF in EIR updates

MGMT_OP_SET_LOCAL_NAME is handled asynchronously on powered controllers
and can run set_name_sync().  When the controller is BR/EDR capable,
set_name_sync() updates the local name and then rebuilds EIR data through
eir_create().  The EIR builder walks hdev->uuids, but the UUID list can
be changed and entries can be freed by MGMT_OP_ADD_UUID and
MGMT_OP_REMOVE_UUID.

pending_eir_or_class() is meant to serialize management commands that
can change EIR or the class of device, but it did not include
MGMT_OP_SET_LOCAL_NAME.  In addition, it walked hdev->mgmt_pending
without hdev->mgmt_pending_lock even though pending commands are added
and removed under that mutex.  A racing command completion can therefore
remove and free a pending command while pending_eir_or_class() is still
inspecting it, leading to a use-after-free in the pending-command list or
allowing a local name update to rebuild EIR while UUID entries are being
removed.

Take hdev->mgmt_pending_lock while scanning hdev->mgmt_pending and treat
MGMT_OP_SET_LOCAL_NAME as an EIR/class-affecting pending command on the
powered asynchronous path.  Check for a conflicting pending command before
copying the new short name so a rejected SET_LOCAL_NAME request does not
modify hdev->short_name.

Fixes: 6fe26f694c ("Bluetooth: MGMT: Protect mgmt_pending list with its own lock")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-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:
Zihan Xi
2026-07-28 14:52:31 -04:00
committed by Luiz Augusto von Dentz
parent d0a7b48ad0
commit 8f2f62855a
+18 -2
View File
@@ -2696,18 +2696,28 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev,
static bool pending_eir_or_class(struct hci_dev *hdev)
{
struct mgmt_pending_cmd *cmd;
bool pending = false;
mutex_lock(&hdev->mgmt_pending_lock);
list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
switch (cmd->opcode) {
case MGMT_OP_ADD_UUID:
case MGMT_OP_REMOVE_UUID:
case MGMT_OP_SET_DEV_CLASS:
case MGMT_OP_SET_LOCAL_NAME:
case MGMT_OP_SET_POWERED:
return true;
pending = true;
break;
}
if (pending)
break;
}
return false;
mutex_unlock(&hdev->mgmt_pending_lock);
return pending;
}
static const u8 bluetooth_base_uuid[] = {
@@ -4071,6 +4081,12 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data,
goto failed;
}
if (hdev_is_powered(hdev) && pending_eir_or_class(hdev)) {
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LOCAL_NAME,
MGMT_STATUS_BUSY);
goto failed;
}
memcpy(hdev->short_name, cp->short_name, sizeof(hdev->short_name));
if (!hdev_is_powered(hdev)) {