← Back to CVE Explorer

CVE-2026-19939

Full vulnerability record from the local CVENow database.

Description

The AVDTP signalling layer arms a 6-second command timeout (AVDTP_TIMEOUT) in avdtp_send_cmd() whenever the local device issues an AVDTP request. Every response handler in subsys/bluetooth/host/classic/avdtp.c cancels session->timeout_work before clearing the pending request, but bt_avdtp_l2cap_disconnected() only cleared session->req and left the delayable work armed. The struct bt_avdtp session is the first member of struct bt_a2dp, whose instances live in a static per-connection array, and a2dp_get_connection() in subsys/bluetooth/host/classic/a2dp.c wipes a slot with memset() as soon as session->br_chan.chan.conn is NULL — which the L2CAP layer arranges immediately after the disconnect callback returns. A remote Bluetooth BR/EDR peer triggers the condition by opening the AVDTP signalling channel, letting the local application issue an AVDTP procedure (the standard A2DP flow issues bt_a2dp_discover() from the connected callback), then tearing the signalling channel down without responding and re-opening it within the remaining timeout window. The reconnect reaches a2dp_accept() → a2dp_get_connection(), which zeroes the whole struct bt_a2dp while the work item's struct _timeout is still linked into the kernel's global timeout list. Both the disconnect and the reconnect instants are chosen by the attacker, so no race has to be won; an encrypted BR/EDR link is required, and CONFIG_BT_A2DP (experimental) must be enabled. With the default CONFIG_TIMEOUT_BACKEND_DLIST backend the kernel decides a timeout is inactive by testing whether its list node is linked, so after the memset() the kernel treats a node that is still on the global timeout list as free: the announce path pops a node whose links and handler pointer are NULL, and the next bt_work_reschedule() on the recycled session re-inserts the same node into a list that already contains it. The consequence is corruption of the single kernel data structure every timer, timeslice and sleep depends on — writes through zeroed or stale list pointers and a NULL handler dispatch — producing a system-wide crash or hang rather than a fault confined to the A2DP session. The fix cancels session->timeout_work at the top of bt_avdtp_l2cap_disconnected() and moves k_work_init_delayable() from avdtp_send_cmd() to the session-establishment paths bt_avdtp_connect() and bt_avdtp_l2cap_accept(), so the work item is always initialized before a disconnect callback can run.

Severity
MEDIUM
CVSS score
6.3 (v3.1)
Vulnerability status
Awaiting Analysis
Source identifier
Published
2026-10-13 17:17:39.937000
Last modified
2026-11-04 05:18:52.113000
View original CVE JSON
{
  "id": "CVE-2026-19939",
  "sourceIdentifier": "[email protected]",
  "published": "2026-10-13T17:17:39.937",
  "lastModified": "2026-11-04T05:18:52.113",
  "vulnStatus": "Awaiting Analysis",
  "cveTags": [],
  "descriptions": [
    {
      "lang": "en",
      "value": "The AVDTP signalling layer arms a 6-second command timeout (AVDTP_TIMEOUT) in avdtp_send_cmd() whenever the local device issues an AVDTP request. Every response handler in subsys/bluetooth/host/classic/avdtp.c cancels session->timeout_work before clearing the pending request, but bt_avdtp_l2cap_disconnected() only cleared session->req and left the delayable work armed. The struct bt_avdtp session is the first member of struct bt_a2dp, whose instances live in a static per-connection array, and a2dp_get_connection() in subsys/bluetooth/host/classic/a2dp.c wipes a slot with memset() as soon as session->br_chan.chan.conn is NULL — which the L2CAP layer arranges immediately after the disconnect callback returns.\n\nA remote Bluetooth BR/EDR peer triggers the condition by opening the AVDTP signalling channel, letting the local application issue an AVDTP procedure (the standard A2DP flow issues bt_a2dp_discover() from the connected callback), then tearing the signalling channel down without responding and re-opening it within the remaining timeout window. The reconnect reaches a2dp_accept() → a2dp_get_connection(), which zeroes the whole struct bt_a2dp while the work item's struct _timeout is still linked into the kernel's global timeout list. Both the disconnect and the reconnect instants are chosen by the attacker, so no race has to be won; an encrypted BR/EDR link is required, and CONFIG_BT_A2DP (experimental) must be enabled.\n\nWith the default CONFIG_TIMEOUT_BACKEND_DLIST backend the kernel decides a timeout is inactive by testing whether its list node is linked, so after the memset() the kernel treats a node that is still on the global timeout list as free: the announce path pops a node whose links and handler pointer are NULL, and the next bt_work_reschedule() on the recycled session re-inserts the same node into a list that already contains it. The consequence is corruption of the single kernel data structure every timer, timeslice and sleep depends on — writes through zeroed or stale list pointers and a NULL handler dispatch — producing a system-wide crash or hang rather than a fault confined to the A2DP session.\n\nThe fix cancels session->timeout_work at the top of bt_avdtp_l2cap_disconnected() and moves k_work_init_delayable() from avdtp_send_cmd() to the session-establishment paths bt_avdtp_connect() and bt_avdtp_l2cap_accept(), so the work item is always initialized before a disconnect callback can run."
    }
  ],
  "affected": [
    {
      "source": "[email protected]",
      "affectedData": [
        {
          "vendor": "zephyrproject",
          "product": "zephyr",
          "defaultStatus": "unaffected",
          "collectionURL": "https://github.com/zephyrproject-rtos/zephyr",
          "packageName": "zephyr",
          "programFiles": [
            "subsys/bluetooth/host/classic/avdtp.c"
          ],
          "programRoutines": [
            {
              "name": "bt_avdtp_l2cap_disconnected"
            }
          ],
          "versions": [
            {
              "version": "3.7.0",
              "lessThan": "4.5.0",
              "versionType": "semver",
              "status": "affected"
            }
          ]
        }
      ]
    }
  ],
  "metrics": {
    "cvssMetricV31": [
      {
        "source": "[email protected]",
        "type": "Secondary",
        "cvssData": {
          "version": "3.1",
          "vectorString": "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H",
          "baseScore": 6.3,
          "baseSeverity": "MEDIUM",
          "attackVector": "ADJACENT_NETWORK",
          "attackComplexity": "LOW",
          "privilegesRequired": "LOW",
          "userInteraction": "NONE",
          "scope": "UNCHANGED",
          "confidentialityImpact": "NONE",
          "integrityImpact": "LOW",
          "availabilityImpact": "HIGH"
        },
        "exploitabilityScore": 2.1,
        "impactScore": 4.2
      }
    ],
    "ssvcV203": [
      {
        "source": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
        "ssvcData": {
          "timestamp": "2026-10-14T18:49:00.544306Z",
          "id": "CVE-2026-19939",
          "options": [
            {
              "exploitation": "none"
            },
            {
              "automatable": "no"
            },
            {
              "technicalImpact": "partial"
            }
          ],
          "role": "CISA Coordinator",
          "version": "2.0.3"
        }
      }
    ]
  },
  "weaknesses": [
    {
      "source": "[email protected]",
      "type": "Secondary",
      "description": [
        {
          "lang": "en",
          "value": "CWE-825"
        }
      ]
    }
  ],
  "references": [
    {
      "url": "https://github.com/zephyrproject-rtos/zephyr/commit/a10d18c3be9d1a968574dbfbce6baee58ea398e8",
      "source": "[email protected]"
    },
    {
      "url": "https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-9mww-f3h4-mq95",
      "source": "[email protected]"
    }
  ]
}