{
  "schema_version": "1.6.1",
  "id": "RPI-CVE-2026-99239",
  "modified": "2026-11-03T05:30:01Z",
  "upstream": [
    "CVE-2026-99239"
  ],
  "published": "2026-11-02T15:14:24Z",
  "summary": "Bluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn",
  "details": "In the Linux kernel, the following vulnerability has been resolved:\n\nBluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn\n\nl2cap_chan may be linked to l2cap_conn at most once. This is assumed in\nseveral places, eg l2cap_chan_del cleanup.\n\nThere is a TOCTOU race where the invariant is violated:\n\n    [Task 1]                          [Task 2]\n    l2cap_chan_connect                l2cap_sock_bind\n      l2cap_chan_lock                   lock_sock\n      l2cap_state_change                if (sk->sk_state != BT_OPEN)\n        chan->state = BT_CONNECT\n        l2cap_sock_state_change_cb      chan->state = BT_BOUND\n                                        sk->sk_state = BT_BOUND\n          lock_sock <------------------ release_sock\n          sk->sk_state = BT_CONNECT\n\nl2cap_sock_connect() does not check sk->sk_state, so since chan->state\nis now BT_BOUND, subsequent connect() ends up with second\n__l2cap_chan_add.\n\nExplicitly document and check the invariant in __l2cap_chan_add with\nWARN_ON_ONCE. The only callsite where it could be hit is\nl2cap_chan_connect, so add pre-check there to avoid relying on\nchan->state. chan->state read/write is not properly guarded currently so\nthere can be other TOCTOUC problems.\n\nAdd l2cap_lock_chan in l2cap_sock_bind() to guard chan->state write.",
  "severity": [
    {
      "type": "CVSS_V3",
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
    }
  ],
  "affected": [
    {
      "package": {
        "ecosystem": "Raspberry Pi OS:12",
        "name": "linux"
      },
      "versions": [
        "1:6.12.109-1+rpt1"
      ],
      "ecosystem_specific": {
        "binaries": [
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-2712",
            "binary_version": "1:6.12.109-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-v6",
            "binary_version": "1:6.12.109-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-v7",
            "binary_version": "1:6.12.109-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-v7l",
            "binary_version": "1:6.12.109-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-v8",
            "binary_version": "1:6.12.109-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.12.109+rpt-rpi-v8-rt",
            "binary_version": "1:6.12.109-1+rpt1"
          }
        ]
      },
      "database_specific": {
        "per_version": {
          "1:6.12.109-1+rpt1": {
            "linux_commit": "7d1826930811232688a50c99c540fbb137aed081",
            "upstream_version": "6.12.109",
            "assessment": "commit ancestry (strak rule)",
            "upstream_fixed": "6.12.115"
          }
        }
      }
    },
    {
      "package": {
        "ecosystem": "Raspberry Pi OS:13",
        "name": "linux"
      },
      "versions": [
        "1:6.18.39-1+rpt1",
        "1:6.18.50-1+rpt1"
      ],
      "ecosystem_specific": {
        "binaries": [
          {
            "binary_name": "linux-image-6.18.39+rpt-rpi-2712",
            "binary_version": "1:6.18.39-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.39+rpt-rpi-v6",
            "binary_version": "1:6.18.39-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.39+rpt-rpi-v7",
            "binary_version": "1:6.18.39-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.39+rpt-rpi-v8",
            "binary_version": "1:6.18.39-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.39+rpt-rpi-v8-rt",
            "binary_version": "1:6.18.39-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.50+rpt-rpi-2712",
            "binary_version": "1:6.18.50-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.50+rpt-rpi-v6",
            "binary_version": "1:6.18.50-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.50+rpt-rpi-v7",
            "binary_version": "1:6.18.50-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.50+rpt-rpi-v8",
            "binary_version": "1:6.18.50-1+rpt1"
          },
          {
            "binary_name": "linux-image-6.18.50+rpt-rpi-v8-rt",
            "binary_version": "1:6.18.50-1+rpt1"
          }
        ]
      },
      "database_specific": {
        "per_version": {
          "1:6.18.39-1+rpt1": {
            "linux_commit": "60ea684a8ace97bb0db1a16e20753bdd6ab371ff",
            "upstream_version": "6.18.39",
            "assessment": "commit ancestry (strak rule)",
            "upstream_fixed": "6.18.60"
          },
          "1:6.18.50-1+rpt1": {
            "linux_commit": "cff533aec2fa601846766b32ff57204e0a61bed7",
            "upstream_version": "6.18.50",
            "assessment": "commit ancestry (strak rule)",
            "upstream_fixed": "6.18.60"
          }
        }
      }
    }
  ],
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://www.cve.org/CVERecord?id=CVE-2026-99239"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/85cc289eb08afa975375f838187369e68b429ee0"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/820579652b24ce277e0097cd65b42bd9b7b8bf07"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/e618d928ec7f88cbeadfa3542adcac40f474e629"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/fabaff1c59ff2cc89cb7e5bd0186a5e15f0c73b2"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/52fe88e4ef87311eee32b45c65603e7c69e75e7d"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/10c705dd706aa93164ba41f2566f15206d6c178e"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/73820561d8cbfd3364a29b8fa77f1707f26029da"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/d3afd7803f9bc2a8c4270199497b60158b2fc647"
    }
  ],
  "database_specific": {
    "source": "https://git.kernel.org/pub/scm/linux/security/vulns.git",
    "upstream_modified": "2026-11-02T16:03:24Z",
    "severity": {
      "source": "kernel CNA",
      "cvss_v3_score": 7.8,
      "cvss_v3_severity": "High",
      "scenario": "AV:L - The double add is a local TOCTOU: l2cap_sock_connect()\u2192l2cap_chan_connect()\u2192__l2cap_chan_add() races l2cap_sock_bind(), which overwrites chan->state to BT_BOUND so a later connect() links the same l2cap_chan again. No received L2CAP/HCI PDU carries that state; a radio peer cannot issue those syscalls.\nAC:L - The attacker owns both threads. bind() holds lock_sock while l2cap_state_change() blocks in l2cap_sock_state_change_cb(); bind() then sets chan->state=BT_BOUND. l2cap_sock_connect() never checks sk->sk_state, so a later connect() observes BT_BOUND and calls __l2cap_chan_add() again. The race is attacker-driven and retryable.\nPR:L - l2cap_sock_create() requires capable(CAP_NET_RAW) only for SOCK_RAW; SOCK_SEQPACKET/STREAM used here have no capability gate. l2cap_add_psm() auto-picks a dynamic PSM (BR/EDR >=0x1001) without CAP_NET_BIND_SERVICE. bt_sock_create() is init_net-only, so this is an ordinary unprivileged local user.\nUI:N - The attacker opens their own AF_BLUETOOTH/BTPROTO_L2CAP socket and issues bind()/connect(). hci_connect_acl() creates the hci_conn, l2cap_conn_add() publishes l2cap_data, and __l2cap_chan_add() runs before any remote Connection Complete or pairing prompt.\nS:U - __l2cap_chan_add() corrupts the host kernel l2cap_chan.list linked into l2cap_conn.chan_l, which l2cap_chan_del() and l2cap_conn_del() then walk; the impact stays in the kernel L2CAP/HCI authority and does not cross a VM, IOMMU, or sandbox boundary.\nC:H - A second __l2cap_chan_add() to another l2cap_conn list_add_tail()s the same chan->list without unlinking it from the first conn->chan_l. l2cap_conn_del()'s list_for_each_entry_safe then follows those stale pointers and treats the other conn's list head as an l2cap_chan, an adjacent-slab type confusion that can be sprayed for a kernel read.\nI:H - l2cap_chan_del() list_del()s the twice-inserted chan->list, writing next/prev of the other connection's nodes, and l2cap_conn_del() calls chan->ops->teardown/close on the type-confused object. That unlink write plus ops-pointer invocation is a write/control-flow primitive.\nA:H - Inserting chan->list into the same conn->chan_l twice makes a self-loop, so __l2cap_get_chan_by_scid(), l2cap_conn_start(), and l2cap_conn_del() hang in list_for_each_entry; after list_del() the leftover head still points at the poisoned node and oopses."
    }
  }
}
