From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.210.170 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793623684; b=aeQAwxU2INmynxYUVO5o1e11W4NJWEMdKhm6OjBsfRJhIDlGAC+AzMZagKhw72s+deW8 bbZB6TXXEl8JG3h3sgHCD09NCGYvaErEbgqn3lsV+zhBHOdCSxfH2uOQ8YhitXIMFjPyo mGhWtHVe2MuolvW0rbg1d+GhVV6nrgiR9qvQZ/tyeCcvapATHrTgg3bI2IIvimukWckG9 l9MpPxNy4R9vBCwJu4GVED+JhMz3j853UFys19NCWX+wiCsluy7asUle43QBi/L53hr6d HI0En2OqfE+CxjZXbBvgXPSSkKyEtQiIAi0AducxfValMgevgRdjtO1IqUPHwnxwhHQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793623684; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=UQh3Hhv6qjNy92jmqlHZeQDSexdAkfgjQwGaT5rQ7nk=; b=mRFJyC+qBaNnkO5wyy+m6cyIO5Kg+eOxA1Y42gSmWliYZFf+8/qk459sV1CxniCDNj2V loNUUNL71zlEcGrnVLQSChK1GddNI2zQP/03JpL4kIJ1cSjVCcATIpSF1Y79kbznLEquz ctSrHekkd/hBo8SBQaJ88BRALznVRh4lSjZcYR4jsiQ/uNHiRKmutvx26PfQObHHrNSXd xFO5KVXzyQD1q/uVmxD5EVCdLPaFxrotCvukMbbH2xCcIK67mwfHvLm9xSYt8Xh6VTgSU 0c/yNPbHb7ItPt/f99etABxwnXN15u7vu7TFsEPUQ16qQ/BEWRbUlv609kYErbQiCgg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=llkcERm3; arc=none smtp.remote-ip=209.85.210.170 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 740F0606A32E for ; Mon, 2 Nov 2026 12:48:04 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 740F0606A32E Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=llkcERm3 Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-8a0880a46e7so1434973b3a.1 for ; Mon, 02 Nov 2026 04:48:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793623684; x=1794228484; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=UQh3Hhv6qjNy92jmqlHZeQDSexdAkfgjQwGaT5rQ7nk=; b=llkcERm3dY9S+MXVNxFUy6yIL5WHeYI2OEQd0kAGizxhHB5Zp+hfiHvATonAfBupC0 dxlrdTgAaKlcqgPcb0pfmgsLo/Wy45Gp5sXLvMANUXNILOXNXD9lGaxJV9nAA+ytouyE W0NbnNAECzZqxpXysgc4TZB8qw3qSNfp0VXNxyirQyAGPoffSs5l0c79D5vA1R9WTrB/ rQhyjZvDVwJhzBC3l7cUqKavl8QvVs8rtkOvxMPHLo5DNp5zhM/XDJcqz3gGiw81WfK5 GhkcFNe7gE4xX5/4uHm9pw1L2V4rLARGb05Xap/3EFUlmC80QTcAuBVWtDzH1E67MPG5 1JxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793623684; x=1794228484; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=UQh3Hhv6qjNy92jmqlHZeQDSexdAkfgjQwGaT5rQ7nk=; b=pZtg8wjQMTHI4cILFgr2+y1yA3x9FaAa6Nm4aGaoroIfadmlIjXPK8r4JM8D/uyRvT nqj6VMnLSS+C6ZJJuNC/UJWCfcYhykvBA2wXSluRxsl6rilRyfE9+0VFkXw545EERRy7 sYLuJEl+KqHxMQOOG59FBZPe5qdJsw2rANUrdA0G0FQuJ1KoqL5ppOj8+IGRb6bRESzz NWW3x5HAOi9Cg5sZJDPAgWdYE77zzgvsQCeSmv47ZfVCoHUh1XMs6xKZ+xHdWl8bbSuW XOs2/zpUzCnErdIFa2UCyr9TsTNnsvJDtePLlNBfa824MCv9wKDkefo1AIG8XP/VtLm9 eK5Q== X-Forwarded-Encrypted: i=1; AKwUvByBM/UEHFdtnIBivTDnvSRsj62X7ZAPTkSZ3lerGXdazcmKoMYRisysMsjwYIYc7Fg1u/4Ca1j1E55AlnU=@vger.kernel.org X-Gm-Message-State: APuU4Z/CSC9RtC50xPULpmzfHmty83khUjnkhTHtALBV0fiPCDxgR5cs zT8bh/L2FSeIPZJ2/w5apiHx86fTC4d/BMsc2UMNs396tV33f9caMgRZ X-Gm-Gg: AYBFou2LYxnX3MkFex0ecxy7SqDLXke13UfXn8HxtLoF7RC2hEsp7VwYCXUiMpgK+ef rEvUJ8JOMTpaDbr11zSDzIrpGR7Az23/1rlpIibBgQJJ7tEevefkrAGEqxa6O1PtD5tRtJNFtcY ItrBJM5X0MOSNb5oGrEH+smEu6tDxKi03TCL/6y7bZRvZlBaJOrZVFcRvyKFePA08Rrz7jW8N7f KhoY0QQBXGmqK7lpBBs8RFT1+4lgTQUrq3hG0mF+hBQ7Ulj2PTsnQ0bi3AHtjziJGVR+mUv92KD Voh5t2BsRbKm0zojLRLrmSpEOx7jcZAA5vhWW518IfhIuERXh+h/bsDNsyhkCwlTnLG+O7ssy2G Q5i4Km8l1mUQZM8mBinJlpbduYHbhWWCWEK4UcUHz4fsMrSKv8/r2e/mdyWv70k53Ol558L6Qbs tSo/eeJ59m+te6cbqdaU2xUiENQDd6QIWGugjj/SNz77iMoF9/KKizYh3P9INn40JMdEGJmkogd yatkd6aXAsDx4b+VjXeGq3Cl5Rmd+rC3ySffushZ9/EKqClA7HKkVfYaKwNpsmHnffZG1tmCbrO k4i3APUMQWeofXSR0ZYryLJmoI4R1WeJm8tR1l9RvS6dAgOEF6xAn+FDQ1mL9bNpdIqTMdC6KAY sLKJrNor75Ea9YA== X-Received: by 2002:a05:6a00:1382:b0:8a0:31e5:8cc3 with SMTP id d2e1a72fcca58-8a8317dd858mr7535283b3a.10.1793623683085; Mon, 02 Nov 2026 04:48:03 -0800 (PST) Received: from KRHW1CJW23.bytedance.net ([139.177.225.227]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-8aad77fda1asm1908357b3a.6.2026.11.02.04.47.58 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 02 Nov 2026 04:48:01 -0800 (PST) From: Zhao Li To: Marcel Holtmann , Luiz Augusto von Dentz Cc: Johan Hedberg , security@kernel.org, linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH 1/2] Bluetooth: L2CAP: Only accept LE responses to outstanding requests Date: Mon, 2 Nov 2026 20:47:34 +0800 Message-ID: <20261102124735.23199-2-enderaoelyther@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261102124735.23199-1-enderaoelyther@gmail.com> References: <20261102124735.23199-1-enderaoelyther@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The LE and ECRED response handlers match channels by identifier without checking whether they sent the request. An incoming channel stores the identifier chosen by the peer, so an unsolicited response can bypass deferred setup, delete an established channel, or leave an unaccepted socket and channel allocated after l2cap_chan_del(). Extend the BR/EDR lookup from commit a518b9a9ef6e ("Bluetooth: L2CAP: Only accept connection responses to outstanding requests") to LE. Require the matching connect-request flag and add a reconfigure-request flag. Check reconfigure identifiers under the channel lock, since BT_RCVMTU can replace a request without the connection lock. Match all members of a rejected ECRED request, which share one identifier. A reconfigure request can outlive close(). Complete it even in BT_DISCONN, and call the close callback after deletion to release an orphaned socket. Otherwise deletion cancels the timer and removes the channel before the disconnect response or the channel timeout can close it. AI assistance was used. Fixes: f1496dee9cbd ("Bluetooth: Add initial code for LE L2CAP Connect Request") Fixes: 71fb419724fa ("Bluetooth: Fix handling of L2CAP Command Reject over LE") Fixes: 15f02b910562 ("Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode") Cc: # 5.7.x: a518b9a9ef6e: Bluetooth: L2CAP: Only accept connection responses to outstanding requests Cc: # 5.7.x Signed-off-by: Zhao Li --- Built in patch order at W=1 with GCC 12.2 (x86_64 module, x86_64 built-in and i386 module) and Clang 19.1.7 (x86_64 module). The x86_64 module builds with both compilers also linked vmlinux and modules. Sparse checked l2cap_core.c with C=2 after each patch without diagnostics. Smatch reported the same pre-existing l2cap_chan_connect() diagnostic as the base; no cross-function database was used. Coccinelle 1.3's use_after_iter rule found nothing in the changed functions. All changed functions were processed; five unchanged parser gaps also occur in the base. Hardware validation has not been run. This diff does not apply to stable branches as is; adapted backports compiled at W=1 on the seven supported lines and, with patch 2's backport applied, linked as modules. Their behaviour was not tested. QEMU/vhci testing with patch 1 alone kept all 445 established channels alive. In 40-pair close-before runs, failed reconfigure responses and Command Rejects each reached 44 l2cap_sock_destruct calls with the fix and four base boot-noise calls; success reached 43 on both. A 1200-pair base run retained 1984 kB of gross Slab memory at listener close while the fixed arms released memory. Kmemleak reported nothing even on the leaking base, so it was not used for the leak verdict. A KCSAN kernel with both patches, built without KASAN, ran the same close-before workloads without data-race reports. The stable request covers 5.7 and newer with the declared BR/EDR lookup prerequisite or an equivalent backport. Older LE-only trees need an adapted patch without ECRED hunks. The diff exceeds the stable guideline of 100 lines including context. include/net/bluetooth/l2cap.h | 1 + net/bluetooth/l2cap_core.c | 82 +++++++++++++++++------------------ 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 54eddcdd341f..2a05bda8ab43 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -756,6 +756,7 @@ enum { FLAG_DEFER_SETUP, FLAG_LE_CONN_REQ_SENT, FLAG_ECRED_CONN_REQ_SENT, + FLAG_ECRED_RECONF_REQ_SENT, FLAG_PENDING_SECURITY, FLAG_HOLD_HCI_CONN, FLAG_DEL, diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 4dd56e0074fd..a21b2d70ad8a 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -144,24 +144,12 @@ static struct l2cap_chan *l2cap_get_chan_by_dcid(struct l2cap_conn *conn, return c; } -static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn, - u8 ident) - __must_hold(&conn->lock) -{ - struct l2cap_chan *c; - - list_for_each_entry(c, &conn->chan_l, list) { - if (c->ident == ident) - return c; - } - return NULL; -} - /* Our connection request has been sent and is still outstanding */ static bool l2cap_chan_connect_pending(struct l2cap_chan *chan) { return chan->state == BT_CONNECT && - test_bit(CONF_CONNECT_PEND, &chan->conf_state); + (test_bit(CONF_CONNECT_PEND, &chan->conf_state) || + test_bit(FLAG_LE_CONN_REQ_SENT, &chan->flags)); } /* Other channels may hold the same ident for a response they still have @@ -174,7 +162,7 @@ static struct l2cap_chan *__l2cap_get_connect_chan_by_ident(struct l2cap_conn *c struct l2cap_chan *c; list_for_each_entry(c, &conn->chan_l, list) { - if (c->ident == ident && l2cap_chan_connect_pending(c)) + if (l2cap_chan_connect_pending(c) && c->ident == ident) return c; } return NULL; @@ -5073,7 +5061,7 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn, BT_DBG("dcid 0x%4.4x mtu %u mps %u credits %u result 0x%2.2x", dcid, mtu, mps, credits, result); - chan = __l2cap_get_chan_by_ident(conn, cmd->ident); + chan = __l2cap_get_connect_chan_by_ident(conn, cmd->ident); if (!chan) return -EBADSLT; @@ -5621,9 +5609,10 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, struct l2cap_chan *orig; u16 dcid; - if (chan->ident != cmd->ident || + if (chan->state != BT_CONNECT || chan->mode != L2CAP_MODE_EXT_FLOWCTL || - chan->state == BT_CONNECTED) + !test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags) || + chan->ident != cmd->ident) continue; l2cap_chan_hold(chan); @@ -5842,16 +5831,9 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn, BT_DBG("result 0x%4.4x", result); - if (!result) { - list_for_each_entry(chan, &conn->chan_l, list) { - if (chan->ident == cmd->ident) - chan->ident = 0; - } - return 0; - } - list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) { - if (chan->ident != cmd->ident) + /* Reconfiguration updates ident with only chan->lock held. */ + if (data_race(READ_ONCE(chan->ident)) != cmd->ident) continue; if (!l2cap_chan_hold_unless_zero(chan)) @@ -5860,7 +5842,15 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn, lockdep_assert_held(&chan->conn->lock); - l2cap_chan_del(chan, ECONNRESET); + if (chan->ident == cmd->ident && + test_and_clear_bit(FLAG_ECRED_RECONF_REQ_SENT, &chan->flags)) { + if (result) { + l2cap_chan_del(chan, ECONNRESET); + chan->ops->close(chan); + } else { + chan->ident = 0; + } + } l2cap_chan_unlock(chan); l2cap_chan_put(chan); @@ -5875,26 +5865,35 @@ static inline int l2cap_le_command_rej(struct l2cap_conn *conn, __must_hold(&conn->lock) { struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data; - struct l2cap_chan *chan; + struct l2cap_chan *chan, *tmp; if (cmd_len < sizeof(*rej)) return -EPROTO; - chan = __l2cap_get_chan_by_ident(conn, cmd->ident); - if (!chan) - goto done; + list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) { + /* Reconfiguration updates ident with only chan->lock held. */ + if (data_race(READ_ONCE(chan->ident)) != cmd->ident) + continue; - chan = l2cap_chan_hold_unless_zero(chan); - if (!chan) - goto done; + if (!l2cap_chan_hold_unless_zero(chan)) + continue; - l2cap_chan_lock(chan); - lockdep_assert_held(&chan->conn->lock); - l2cap_chan_del(chan, ECONNREFUSED); - l2cap_chan_unlock(chan); - l2cap_chan_put(chan); + l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + + if (chan->ident == cmd->ident && + ((chan->state == BT_CONNECT && + (test_bit(FLAG_LE_CONN_REQ_SENT, &chan->flags) || + test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))) || + test_bit(FLAG_ECRED_RECONF_REQ_SENT, &chan->flags))) { + l2cap_chan_del(chan, ECONNREFUSED); + chan->ops->close(chan); + } + + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + } -done: return 0; } @@ -7757,6 +7756,7 @@ static void l2cap_ecred_reconfigure(struct l2cap_chan *chan) pdu->scid[0] = cpu_to_le16(chan->scid); chan->ident = l2cap_get_ident(conn); + set_bit(FLAG_ECRED_RECONF_REQ_SENT, &chan->flags); l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ, struct_size(pdu, scid, 1), pdu); -- 2.56.0