From: howardchung <howardchung@xxxxxxxxxx> Attack scenario: 1. A Chromebook (let's call this device A) is paired to a legitimate Bluetooth classic device (e.g. a speaker) (let's call this device B). 2. A malicious device (let's call this device C) pretends to be the Bluetooth speaker by using the same BT address. 3. If device A is not currently connected to device B, device A will be ready to accept connection from device B in the background (technically, doing Page Scan). 4. Therefore, device C can initiate connection to device A (because device A is doing Page Scan) and device A will accept the connection because device A trusts device C's address which is the same as device B's address. 5. Device C won't be able to communicate at any high level Bluetooth profile with device A because device A enforces that device C is encrypted with their common Link Key, which device C doesn't have. But device C can initiate pairing with device A with just-works model without requiring user interaction (there is only pairing notification). After pairing, device A now trusts device C with a new different link key, common between device A and C. 6. From now on, device A trusts device C, so device C can at anytime connect to device A to do any kind of high-level hijacking, e.g. speaker hijack or mouse/keyboard hijack. To fix this, reject the pairing if all the conditions below are met. - the pairing is initialized by peer - the authorization method is just-work - host already had the link key to the peer Also create a debugfs option to permit the pairing even the conditions above are met. Signed-off-by: howardchung <howardchung@xxxxxxxxxx> --- include/net/bluetooth/hci.h | 1 + net/bluetooth/hci_core.c | 47 +++++++++++++++++++++++++++++++++++++ net/bluetooth/hci_event.c | 12 ++++++++++ 3 files changed, 60 insertions(+) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 07b6ecedc6ce..4918b79baa41 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -283,6 +283,7 @@ enum { HCI_FORCE_STATIC_ADDR, HCI_LL_RPA_RESOLUTION, HCI_CMD_PENDING, + HCI_PERMIT_JUST_WORK_REPAIR, __HCI_NUM_FLAGS, }; diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 9e19d5a3aac8..9014aa567e7b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -172,10 +172,57 @@ static const struct file_operations vendor_diag_fops = { .llseek = default_llseek, }; +static ssize_t permit_just_work_repair_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct hci_dev *hdev = file->private_data; + char buf[3]; + + buf[0] = hci_dev_test_flag(hdev, HCI_PERMIT_JUST_WORK_REPAIR) ? 'Y' + : 'N'; + buf[1] = '\n'; + buf[2] = '\0'; + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t permit_just_work_repair_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct hci_dev *hdev = file->private_data; + char buf[32]; + size_t buf_size = min(count, (sizeof(buf) - 1)); + bool enable; + + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + buf[buf_size] = '\0'; + if (strtobool(buf, &enable)) + return -EINVAL; + + if (enable) + hci_dev_set_flag(hdev, HCI_PERMIT_JUST_WORK_REPAIR); + else + hci_dev_clear_flag(hdev, HCI_PERMIT_JUST_WORK_REPAIR); + + return count; +} + +static const struct file_operations permit_just_work_repair_fops = { + .open = simple_open, + .read = permit_just_work_repair_read, + .write = permit_just_work_repair_write, + .llseek = default_llseek, +}; + static void hci_debugfs_create_basic(struct hci_dev *hdev) { debugfs_create_file("dut_mode", 0644, hdev->debugfs, hdev, &dut_mode_fops); + debugfs_create_file("permit_just_work_repair", 0644, hdev->debugfs, + hdev, &permit_just_work_repair_fops); if (hdev->set_diag) debugfs_create_file("vendor_diag", 0644, hdev->debugfs, hdev, diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 6ddc4a74a5e4..898e347e19e0 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -4539,6 +4539,18 @@ static void hci_user_confirm_request_evt(struct hci_dev *hdev, goto unlock; } + /* If there already exists link key in local host, terminate the + * connection by default since the remote device could be malicious. + * Permit the connection if permit_just_work_repair is enabled. + */ + if (!hci_dev_test_flag(hdev, HCI_PERMIT_JUST_WORK_REPAIR) && + hci_find_link_key(hdev, &ev->bdaddr)) { + BT_DBG("Rejecting request: local host already have link key"); + hci_send_cmd(hdev, HCI_OP_USER_CONFIRM_NEG_REPLY, + sizeof(ev->bdaddr), &ev->bdaddr); + goto unlock; + } + /* If no side requires MITM protection; auto-accept */ if ((!loc_mitm || conn->remote_cap == HCI_IO_NO_INPUT_OUTPUT) && (!rem_mitm || conn->io_capability == HCI_IO_NO_INPUT_OUTPUT)) { -- 2.24.1.735.g03f4e72817-goog