mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
This attemps to fix possible UAFs caused by struct mgmt_pending being freed while still being processed like in the following trace, in order to fix mgmt_pending_valid is introduce and use to check if the mgmt_pending hasn't been removed from the pending list, on the complete callbacks it is used to check and in addtion remove the cmd from the list while holding mgmt_pending_lock to avoid TOCTOU problems since if the cmd is left on the list it can still be accessed and freed. BUG: KASAN: slab-use-after-free in mgmt_add_adv_patterns_monitor_sync+0x35/0x50 net/bluetooth/mgmt.c:5223 Read of size 8 at addr ffff8880709d4dc0 by task kworker/u11:0/55 CPU: 0 UID: 0 PID: 55 Comm: kworker/u11:0 Not tainted 6.16.4 #2 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014 Workqueue: hci0 hci_cmd_sync_work Call Trace: <TASK> dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xca/0x240 mm/kasan/report.c:482 kasan_report+0x118/0x150 mm/kasan/report.c:595 mgmt_add_adv_patterns_monitor_sync+0x35/0x50 net/bluetooth/mgmt.c:5223 hci_cmd_sync_work+0x210/0x3a0 net/bluetooth/hci_sync.c:332 process_one_work kernel/workqueue.c:3238 [inline] process_scheduled_works+0xade/0x17b0 kernel/workqueue.c:3321 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3402 kthread+0x711/0x8a0 kernel/kthread.c:464 ret_from_fork+0x3fc/0x770 arch/x86/kernel/process.c:148 ret_from_fork_asm+0x1a/0x30 home/kwqcheii/source/fuzzing/kernel/kasan/linux-6.16.4/arch/x86/entry/entry_64.S:245 </TASK> Allocated by task 12210: kasan_save_stack mm/kasan/common.c:47 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:68 poison_kmalloc_redzone mm/kasan/common.c:377 [inline] __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:394 kasan_kmalloc include/linux/kasan.h:260 [inline] __kmalloc_cache_noprof+0x230/0x3d0 mm/slub.c:4364 kmalloc_noprof include/linux/slab.h:905 [inline] kzalloc_noprof include/linux/slab.h:1039 [inline] mgmt_pending_new+0x65/0x1e0 net/bluetooth/mgmt_util.c:269 mgmt_pending_add+0x35/0x140 net/bluetooth/mgmt_util.c:296 __add_adv_patterns_monitor+0x130/0x200 net/bluetooth/mgmt.c:5247 add_adv_patterns_monitor+0x214/0x360 net/bluetooth/mgmt.c:5364 hci_mgmt_cmd+0x9c9/0xef0 net/bluetooth/hci_sock.c:1719 hci_sock_sendmsg+0x6ca/0xef0 net/bluetooth/hci_sock.c:1839 sock_sendmsg_nosec net/socket.c:714 [inline] __sock_sendmsg+0x219/0x270 net/socket.c:729 sock_write_iter+0x258/0x330 net/socket.c:1133 new_sync_write fs/read_write.c:593 [inline] vfs_write+0x5c9/0xb30 fs/read_write.c:686 ksys_write+0x145/0x250 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 12221: kasan_save_stack mm/kasan/common.c:47 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:68 kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:576 poison_slab_object mm/kasan/common.c:247 [inline] __kasan_slab_free+0x62/0x70 mm/kasan/common.c:264 kasan_slab_free include/linux/kasan.h:233 [inline] slab_free_hook mm/slub.c:2381 [inline] slab_free mm/slub.c:4648 [inline] kfree+0x18e/0x440 mm/slub.c:4847 mgmt_pending_free net/bluetooth/mgmt_util.c:311 [inline] mgmt_pending_foreach+0x30d/0x380 net/bluetooth/mgmt_util.c:257 __mgmt_power_off+0x169/0x350 net/bluetooth/mgmt.c:9444 hci_dev_close_sync+0x754/0x1330 net/bluetooth/hci_sync.c:5290 hci_dev_do_close net/bluetooth/hci_core.c:501 [inline] hci_dev_close+0x108/0x200 net/bluetooth/hci_core.c:526 sock_do_ioctl+0xd9/0x300 net/socket.c:1192 sock_ioctl+0x576/0x790 net/socket.c:1313 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:907 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes:cf75ad8b41("Bluetooth: hci_sync: Convert MGMT_SET_POWERED") Fixes:2bd1b23761("Bluetooth: hci_sync: Convert MGMT_OP_SET_DISCOVERABLE to use cmd_sync") Fixes:f056a65783("Bluetooth: hci_sync: Convert MGMT_OP_SET_CONNECTABLE to use cmd_sync") Fixes:3244845c63("Bluetooth: hci_sync: Convert MGMT_OP_SSP") Fixes:d81a494c43("Bluetooth: hci_sync: Convert MGMT_OP_SET_LE") Fixes:b338d91703("Bluetooth: Implement support for Mesh") Fixes:6f6ff38a1e("Bluetooth: hci_sync: Convert MGMT_OP_SET_LOCAL_NAME") Fixes:71efbb08b5("Bluetooth: hci_sync: Convert MGMT_OP_SET_PHY_CONFIGURATION") Fixes:b747a83690("Bluetooth: hci_sync: Refactor add Adv Monitor") Fixes:abfeea476c("Bluetooth: hci_sync: Convert MGMT_OP_START_DISCOVERY") Fixes:26ac4c56f0("Bluetooth: hci_sync: Convert MGMT_OP_SET_ADVERTISING") Reported-by: cen zhang <zzzccc427@gmail.com> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
442 lines
9.6 KiB
C
442 lines
9.6 KiB
C
/*
|
|
BlueZ - Bluetooth protocol stack for Linux
|
|
|
|
Copyright (C) 2015 Intel Corporation
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation;
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
|
|
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
|
|
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
|
|
SOFTWARE IS DISCLAIMED.
|
|
*/
|
|
|
|
#include <linux/unaligned.h>
|
|
|
|
#include <net/bluetooth/bluetooth.h>
|
|
#include <net/bluetooth/hci_core.h>
|
|
#include <net/bluetooth/hci_mon.h>
|
|
#include <net/bluetooth/mgmt.h>
|
|
|
|
#include "mgmt_util.h"
|
|
|
|
static struct sk_buff *create_monitor_ctrl_event(__le16 index, u32 cookie,
|
|
u16 opcode, u16 len, void *buf)
|
|
{
|
|
struct hci_mon_hdr *hdr;
|
|
struct sk_buff *skb;
|
|
|
|
skb = bt_skb_alloc(6 + len, GFP_ATOMIC);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
put_unaligned_le32(cookie, skb_put(skb, 4));
|
|
put_unaligned_le16(opcode, skb_put(skb, 2));
|
|
|
|
if (buf)
|
|
skb_put_data(skb, buf, len);
|
|
|
|
__net_timestamp(skb);
|
|
|
|
hdr = skb_push(skb, HCI_MON_HDR_SIZE);
|
|
hdr->opcode = cpu_to_le16(HCI_MON_CTRL_EVENT);
|
|
hdr->index = index;
|
|
hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE);
|
|
|
|
return skb;
|
|
}
|
|
|
|
struct sk_buff *mgmt_alloc_skb(struct hci_dev *hdev, u16 opcode,
|
|
unsigned int size)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
skb = alloc_skb(sizeof(struct mgmt_hdr) + size, GFP_KERNEL);
|
|
if (!skb)
|
|
return skb;
|
|
|
|
skb_reserve(skb, sizeof(struct mgmt_hdr));
|
|
bt_cb(skb)->mgmt.hdev = hdev;
|
|
bt_cb(skb)->mgmt.opcode = opcode;
|
|
|
|
return skb;
|
|
}
|
|
|
|
int mgmt_send_event_skb(unsigned short channel, struct sk_buff *skb, int flag,
|
|
struct sock *skip_sk)
|
|
{
|
|
struct hci_dev *hdev;
|
|
struct mgmt_hdr *hdr;
|
|
int len;
|
|
|
|
if (!skb)
|
|
return -EINVAL;
|
|
|
|
len = skb->len;
|
|
hdev = bt_cb(skb)->mgmt.hdev;
|
|
|
|
/* Time stamp */
|
|
__net_timestamp(skb);
|
|
|
|
/* Send just the data, without headers, to the monitor */
|
|
if (channel == HCI_CHANNEL_CONTROL)
|
|
hci_send_monitor_ctrl_event(hdev, bt_cb(skb)->mgmt.opcode,
|
|
skb->data, skb->len,
|
|
skb_get_ktime(skb), flag, skip_sk);
|
|
|
|
hdr = skb_push(skb, sizeof(*hdr));
|
|
hdr->opcode = cpu_to_le16(bt_cb(skb)->mgmt.opcode);
|
|
if (hdev)
|
|
hdr->index = cpu_to_le16(hdev->id);
|
|
else
|
|
hdr->index = cpu_to_le16(MGMT_INDEX_NONE);
|
|
hdr->len = cpu_to_le16(len);
|
|
|
|
hci_send_to_channel(channel, skb, flag, skip_sk);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
int mgmt_send_event(u16 event, struct hci_dev *hdev, unsigned short channel,
|
|
void *data, u16 data_len, int flag, struct sock *skip_sk)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
skb = mgmt_alloc_skb(hdev, event, data_len);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
if (data)
|
|
skb_put_data(skb, data, data_len);
|
|
|
|
return mgmt_send_event_skb(channel, skb, flag, skip_sk);
|
|
}
|
|
|
|
int mgmt_cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status)
|
|
{
|
|
struct sk_buff *skb, *mskb;
|
|
struct mgmt_hdr *hdr;
|
|
struct mgmt_ev_cmd_status *ev;
|
|
int err;
|
|
|
|
BT_DBG("sock %p, index %u, cmd %u, status %u", sk, index, cmd, status);
|
|
|
|
skb = alloc_skb(sizeof(*hdr) + sizeof(*ev), GFP_KERNEL);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
hdr = skb_put(skb, sizeof(*hdr));
|
|
|
|
hdr->opcode = cpu_to_le16(MGMT_EV_CMD_STATUS);
|
|
hdr->index = cpu_to_le16(index);
|
|
hdr->len = cpu_to_le16(sizeof(*ev));
|
|
|
|
ev = skb_put(skb, sizeof(*ev));
|
|
ev->status = status;
|
|
ev->opcode = cpu_to_le16(cmd);
|
|
|
|
mskb = create_monitor_ctrl_event(hdr->index, hci_sock_get_cookie(sk),
|
|
MGMT_EV_CMD_STATUS, sizeof(*ev), ev);
|
|
if (mskb)
|
|
skb->tstamp = mskb->tstamp;
|
|
else
|
|
__net_timestamp(skb);
|
|
|
|
err = sock_queue_rcv_skb(sk, skb);
|
|
if (err < 0)
|
|
kfree_skb(skb);
|
|
|
|
if (mskb) {
|
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, mskb,
|
|
HCI_SOCK_TRUSTED, NULL);
|
|
kfree_skb(mskb);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int mgmt_cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status,
|
|
void *rp, size_t rp_len)
|
|
{
|
|
struct sk_buff *skb, *mskb;
|
|
struct mgmt_hdr *hdr;
|
|
struct mgmt_ev_cmd_complete *ev;
|
|
int err;
|
|
|
|
BT_DBG("sock %p", sk);
|
|
|
|
skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + rp_len, GFP_KERNEL);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
hdr = skb_put(skb, sizeof(*hdr));
|
|
|
|
hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE);
|
|
hdr->index = cpu_to_le16(index);
|
|
hdr->len = cpu_to_le16(sizeof(*ev) + rp_len);
|
|
|
|
ev = skb_put(skb, sizeof(*ev) + rp_len);
|
|
ev->opcode = cpu_to_le16(cmd);
|
|
ev->status = status;
|
|
|
|
if (rp)
|
|
memcpy(ev->data, rp, rp_len);
|
|
|
|
mskb = create_monitor_ctrl_event(hdr->index, hci_sock_get_cookie(sk),
|
|
MGMT_EV_CMD_COMPLETE,
|
|
sizeof(*ev) + rp_len, ev);
|
|
if (mskb)
|
|
skb->tstamp = mskb->tstamp;
|
|
else
|
|
__net_timestamp(skb);
|
|
|
|
err = sock_queue_rcv_skb(sk, skb);
|
|
if (err < 0)
|
|
kfree_skb(skb);
|
|
|
|
if (mskb) {
|
|
hci_send_to_channel(HCI_CHANNEL_MONITOR, mskb,
|
|
HCI_SOCK_TRUSTED, NULL);
|
|
kfree_skb(mskb);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
struct mgmt_pending_cmd *mgmt_pending_find(unsigned short channel, u16 opcode,
|
|
struct hci_dev *hdev)
|
|
{
|
|
struct mgmt_pending_cmd *cmd, *tmp;
|
|
|
|
mutex_lock(&hdev->mgmt_pending_lock);
|
|
|
|
list_for_each_entry_safe(cmd, tmp, &hdev->mgmt_pending, list) {
|
|
if (hci_sock_get_channel(cmd->sk) != channel)
|
|
continue;
|
|
|
|
if (cmd->opcode == opcode) {
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
return cmd;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev, bool remove,
|
|
void (*cb)(struct mgmt_pending_cmd *cmd, void *data),
|
|
void *data)
|
|
{
|
|
struct mgmt_pending_cmd *cmd, *tmp;
|
|
|
|
mutex_lock(&hdev->mgmt_pending_lock);
|
|
|
|
list_for_each_entry_safe(cmd, tmp, &hdev->mgmt_pending, list) {
|
|
if (opcode > 0 && cmd->opcode != opcode)
|
|
continue;
|
|
|
|
if (remove)
|
|
list_del(&cmd->list);
|
|
|
|
cb(cmd, data);
|
|
|
|
if (remove)
|
|
mgmt_pending_free(cmd);
|
|
}
|
|
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
}
|
|
|
|
struct mgmt_pending_cmd *mgmt_pending_new(struct sock *sk, u16 opcode,
|
|
struct hci_dev *hdev,
|
|
void *data, u16 len)
|
|
{
|
|
struct mgmt_pending_cmd *cmd;
|
|
|
|
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
|
if (!cmd)
|
|
return NULL;
|
|
|
|
cmd->opcode = opcode;
|
|
cmd->hdev = hdev;
|
|
|
|
cmd->param = kmemdup(data, len, GFP_KERNEL);
|
|
if (!cmd->param) {
|
|
kfree(cmd);
|
|
return NULL;
|
|
}
|
|
|
|
cmd->param_len = len;
|
|
|
|
cmd->sk = sk;
|
|
sock_hold(sk);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
struct mgmt_pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode,
|
|
struct hci_dev *hdev,
|
|
void *data, u16 len)
|
|
{
|
|
struct mgmt_pending_cmd *cmd;
|
|
|
|
cmd = mgmt_pending_new(sk, opcode, hdev, data, len);
|
|
if (!cmd)
|
|
return NULL;
|
|
|
|
mutex_lock(&hdev->mgmt_pending_lock);
|
|
list_add_tail(&cmd->list, &hdev->mgmt_pending);
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
void mgmt_pending_free(struct mgmt_pending_cmd *cmd)
|
|
{
|
|
sock_put(cmd->sk);
|
|
kfree(cmd->param);
|
|
kfree(cmd);
|
|
}
|
|
|
|
void mgmt_pending_remove(struct mgmt_pending_cmd *cmd)
|
|
{
|
|
mutex_lock(&cmd->hdev->mgmt_pending_lock);
|
|
list_del(&cmd->list);
|
|
mutex_unlock(&cmd->hdev->mgmt_pending_lock);
|
|
|
|
mgmt_pending_free(cmd);
|
|
}
|
|
|
|
bool __mgmt_pending_listed(struct hci_dev *hdev, struct mgmt_pending_cmd *cmd)
|
|
{
|
|
struct mgmt_pending_cmd *tmp;
|
|
|
|
lockdep_assert_held(&hdev->mgmt_pending_lock);
|
|
|
|
if (!cmd)
|
|
return false;
|
|
|
|
list_for_each_entry(tmp, &hdev->mgmt_pending, list) {
|
|
if (cmd == tmp)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mgmt_pending_listed(struct hci_dev *hdev, struct mgmt_pending_cmd *cmd)
|
|
{
|
|
bool listed;
|
|
|
|
mutex_lock(&hdev->mgmt_pending_lock);
|
|
listed = __mgmt_pending_listed(hdev, cmd);
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
|
|
return listed;
|
|
}
|
|
|
|
bool mgmt_pending_valid(struct hci_dev *hdev, struct mgmt_pending_cmd *cmd)
|
|
{
|
|
bool listed;
|
|
|
|
if (!cmd)
|
|
return false;
|
|
|
|
mutex_lock(&hdev->mgmt_pending_lock);
|
|
|
|
listed = __mgmt_pending_listed(hdev, cmd);
|
|
if (listed)
|
|
list_del(&cmd->list);
|
|
|
|
mutex_unlock(&hdev->mgmt_pending_lock);
|
|
|
|
return listed;
|
|
}
|
|
|
|
void mgmt_mesh_foreach(struct hci_dev *hdev,
|
|
void (*cb)(struct mgmt_mesh_tx *mesh_tx, void *data),
|
|
void *data, struct sock *sk)
|
|
{
|
|
struct mgmt_mesh_tx *mesh_tx, *tmp;
|
|
|
|
list_for_each_entry_safe(mesh_tx, tmp, &hdev->mesh_pending, list) {
|
|
if (!sk || mesh_tx->sk == sk)
|
|
cb(mesh_tx, data);
|
|
}
|
|
}
|
|
|
|
struct mgmt_mesh_tx *mgmt_mesh_next(struct hci_dev *hdev, struct sock *sk)
|
|
{
|
|
struct mgmt_mesh_tx *mesh_tx;
|
|
|
|
if (list_empty(&hdev->mesh_pending))
|
|
return NULL;
|
|
|
|
list_for_each_entry(mesh_tx, &hdev->mesh_pending, list) {
|
|
if (!sk || mesh_tx->sk == sk)
|
|
return mesh_tx;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct mgmt_mesh_tx *mgmt_mesh_find(struct hci_dev *hdev, u8 handle)
|
|
{
|
|
struct mgmt_mesh_tx *mesh_tx;
|
|
|
|
if (list_empty(&hdev->mesh_pending))
|
|
return NULL;
|
|
|
|
list_for_each_entry(mesh_tx, &hdev->mesh_pending, list) {
|
|
if (mesh_tx->handle == handle)
|
|
return mesh_tx;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct mgmt_mesh_tx *mgmt_mesh_add(struct sock *sk, struct hci_dev *hdev,
|
|
void *data, u16 len)
|
|
{
|
|
struct mgmt_mesh_tx *mesh_tx;
|
|
|
|
mesh_tx = kzalloc(sizeof(*mesh_tx), GFP_KERNEL);
|
|
if (!mesh_tx)
|
|
return NULL;
|
|
|
|
hdev->mesh_send_ref++;
|
|
if (!hdev->mesh_send_ref)
|
|
hdev->mesh_send_ref++;
|
|
|
|
mesh_tx->handle = hdev->mesh_send_ref;
|
|
mesh_tx->index = hdev->id;
|
|
memcpy(mesh_tx->param, data, len);
|
|
mesh_tx->param_len = len;
|
|
mesh_tx->sk = sk;
|
|
sock_hold(sk);
|
|
|
|
list_add_tail(&mesh_tx->list, &hdev->mesh_pending);
|
|
|
|
return mesh_tx;
|
|
}
|
|
|
|
void mgmt_mesh_remove(struct mgmt_mesh_tx *mesh_tx)
|
|
{
|
|
list_del(&mesh_tx->list);
|
|
sock_put(mesh_tx->sk);
|
|
kfree(mesh_tx);
|
|
}
|