From nobody Sat Sep 26 11:46:31 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A75A419C54E; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; cv=none; b=tHp/p+Zm4d/Xoai98UDyskFnnxT3HThugLsaEx/addHqFKT5OlxTNzBkf9jyrD7wWfp0eMeZYuuQDNHs+nRQMp5wNpbprwww0/85gaBcK99u2S4XTAPQSRg8TA/TbNy2e6vBNqk4fY6IDYkgZgbtUmoq81dbO1F4T8iKpBLtorA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; c=relaxed/simple; bh=7cvm2vOol8idVJWZnrMP4RGnxDFZaUNOVWCm6BtCa+k=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=PWhLD13XXg3aCuEd3TF9M4Fm6+dJRVLCCrQ6r2nKmT3gcLJzOKAgeQTGjUri5bBvlsfNRwMhkko1yDNz6hz+wBYu9LCz/8J+nTcq2vg6U8aeVpsKKwBT2ANkQpsVcSb45ey4vESFkZAX+KW/qtlyiSntSSpQYgXJ0K3zvejmtjM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ouSAjPpQ; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ouSAjPpQ" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 38D881F00A3A; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788296588; bh=0RvE8748AK5qj+VB4OtV9nO1Jn4eXX0WI3dq4glcEj8=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ouSAjPpQHHZQ1jYB7zuKTFzGgW+QXEwWCeita8hgVXuoH92aTjeURSYAayRpvwGuD 56BFgnwWVUyX1fp8zd8KW867XZGKuE2rqefixMf/lR7tgRVL7G7V3Fn7YKS+X8jAjQ XPnAaJdWp9m01dP/2XrIF98n2uEwfpvPEcEj5gnexMUK6I9eNSnJU3zfx5zTSyH7ra mV/HMJmwgOx20HJVhnUaZT3YzPXND1dvti/dMV+kR0vKqB67y3jYo7gSYf4To/wdzp zWG8tznuGhrAc2eZcCyWWipXrConxUbh7G6c22GDGxFbnK8fdk3sPUUs7i9WZHPiX8 6CmhfNe6DldJQ== From: Kees Cook To: Jakub Kicinski Cc: Kees Cook , syzbot+628f93722c08dc5aabe0@syzkaller.appspotmail.com, "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Xuanqiang Luo , Tim Bird , Zihan Xi , linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next 1/3] llc: do not run the state machine on out of service connections Date: Tue, 1 Sep 2026 14:03:03 -0700 Message-Id: <20260901210308.1173180-1-kees@kernel.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260901210300.i.590-kees@kernel.org> References: <20260901210300.i.590-kees@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=6181; i=kees@kernel.org; h=from:subject; bh=7cvm2vOol8idVJWZnrMP4RGnxDFZaUNOVWCm6BtCa+k=; b=owGbwMvMwCVmps19z/KJym7G02pJDFnTbTvkky85F54vKbZ59XpKk8fEhK/PTcJqb7GaT7l1N fxA7rPwjlIWBjEuBlkxRZYgO/c4F4+37eHucxVh5rAygQxh4OIUgIk8fc/IMPFHFXPU9Fyeh9+l ZI4r3YqX8HZt7/KuZ3i/uunyeTH/SYwM2625TB0ir7pYHl9VcfbwYo1tkl5qUq/v7gnzKuhuDrn DCQA= X-Developer-Key: i=kees@kernel.org; a=openpgp; fpr=A5C3F68F229DD60F723E6E138972F4DFDC6DC026 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" syzbot reported an out of bounds access of llc_conn_state_table[] and llc_offset_table[] reached from the LLC receive path: BUG: KASAN: global-out-of-bounds in llc_qualify_conn_ev net/llc/llc_conn.= c:394 Read of size 8 at addr ffff800089e50078 by task syz.0.17/4975 The buggy address belongs to the variable: llc_temp_state_transitions+0x58/0x60 UBSAN: array-index-out-of-bounds in net/llc/llc_conn.c:681:8 index -1 is out of range for type 'int[12][5]' Both tables are indexed with "llc->state - 1" because connection states are 1-based, and LLC_CONN_OUT_OF_SVC is 0, so a connection in that pseudo state indexes them with -1. LLC_CONN_OUT_OF_SVC is not a state of the state machine: it means the connection component does not exist, either because it has not been brought up or because it has been torn down, and it has no row in llc_conn_state_table[]. A socket can nevertheless be left in it while remaining reachable: llc_adm_state_trans_5 (LLC_CONN_STATE_ADM, "receive any frame") .next_state =3D LLC_CONN_OUT_OF_SVC .ev_actions =3D { llc_conn_disc } llc_conn_disc() is documented as "removes connection from SAP list and frees it", but it has been a stub returning 0 since 1da177e4c3f4 ("Linux-2.6.12-rc2"). So the first unexpected frame for a socket sitting in LLC_CONN_STATE_ADM - a bound socket, or a passive open child created by llc_create_incoming_sock() - moves it to LLC_CONN_OUT_OF_SVC and leaves it hashed in its SAP. Every following frame is found again by __llc_lookup_established() and dispatched into the state machine with state 0. That is what the reproducer does: bind a PF_LLC socket and inject the same 802.2 frame twice. "An out of service connection must not be given events" is already the rule, but it is enforced at only some of the entry points: llc_backlog_rcv() tests "llc->state > 1", and llc_process_tmr_ev() and llc_send_disc() test LLC_CONN_OUT_OF_SVC. The direct receive path (llc_conn_handler() -> llc_conn_rcv()) and the upper layer primitive path (llc_establish_connection()) do not, and llc_conn_service()'s own sanity check covers the upper bound only. Enforce the rule once in llc_conn_state_process(), which every event source funnels through, and drop the event there rather than let it reach the tables. Complete llc_conn_service()'s range check as well so that the code doing the indexing cannot underflow either. The socket is still left bricked in LLC_CONN_OUT_OF_SVC, which is the pre-existing behaviour of the state table, but it is no longer a memory safety problem. Build tested ARCH=3Dx86_64 net/llc/ with GCC 14.2.0, CONFIG_LLC2=3Dy and = =3Dm. Reported-by: syzbot+628f93722c08dc5aabe0@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a95888b.4d659fcc.734b4.0051.GAE@google= .com Fixes: 1da177e4c3f41 ("Linux-2.6.12-rc2") Assisted-by: Claude:claude-opus-5[1m] Signed-off-by: Kees Cook --- include/net/llc_c_st.h | 2 +- net/llc/llc_conn.c | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/net/llc_c_st.h b/include/net/llc_c_st.h index f52a4cc4880d..24577e0b17df 100644 --- a/include/net/llc_c_st.h +++ b/include/net/llc_c_st.h @@ -42,5 +42,5 @@ struct llc_conn_state { const struct llc_conn_state_trans **transitions; }; =20 -extern struct llc_conn_state llc_conn_state_table[]; +extern struct llc_conn_state llc_conn_state_table[NBR_CONN_STATES]; #endif /* LLC_C_ST_H */ diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index 260460d50f54..a4ae29b42300 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -36,11 +36,38 @@ static const struct llc_conn_state_trans *llc_qualify_c= onn_ev(struct sock *sk, /* Offset table on connection states transition diagram */ static int llc_offset_table[NBR_CONN_STATES][NBR_CONN_EV]; =20 +/* Both tables are walked together with the same "state - 1" index. */ +static_assert(ARRAY_SIZE(llc_offset_table) =3D=3D ARRAY_SIZE(llc_conn_stat= e_table)); + int sysctl_llc2_ack_timeout =3D LLC2_ACK_TIME * HZ; int sysctl_llc2_p_timeout =3D LLC2_P_TIME * HZ; int sysctl_llc2_rej_timeout =3D LLC2_REJ_TIME * HZ; int sysctl_llc2_busy_timeout =3D LLC2_BUSY_TIME * HZ; =20 +/** + * llc_conn_state_in_service - can this state drive the state machine? + * @state: state of connection + * + * Connection states are 1-based indexes into llc_conn_state_table[] and + * llc_offset_table[]. LLC_CONN_OUT_OF_SVC is not a state of the state + * machine at all: it marks a connection that has no transition table, + * either because it has not been brought up yet or because it has been + * torn down. Returns true if @state has a row in those tables. + */ +static bool llc_conn_state_in_service(u8 state) +{ + /* + * The tables are indexed with "state - 1", so the numbering has to be + * dense, start right after the LLC_CONN_OUT_OF_SVC sentinel, and end + * at NBR_CONN_STATES for the bounds below to be the real ones. + */ + static_assert(LLC_CONN_OUT_OF_SVC =3D=3D 0); + static_assert(LLC_CONN_STATE_ADM =3D=3D LLC_CONN_OUT_OF_SVC + 1); + static_assert(LLC_CONN_STATE_TEMP =3D=3D NBR_CONN_STATES); + + return state > LLC_CONN_OUT_OF_SVC && state <=3D NBR_CONN_STATES; +} + /** * llc_conn_state_process - sends event to connection state machine * @sk: connection @@ -59,6 +86,15 @@ int llc_conn_state_process(struct sock *sk, struct sk_bu= ff *skb) struct llc_sock *llc =3D llc_sk(skb->sk); struct llc_conn_state_ev *ev =3D llc_conn_ev(skb); =20 + /* + * An out of service connection has no row in llc_conn_state_table[], + * so it cannot be driven by any event. + */ + if (unlikely(!llc_conn_state_in_service(llc->state))) { + kfree_skb(skb); + return 1; + } + ev->ind_prim =3D ev->cfm_prim =3D 0; /* * Send event to state machine @@ -354,7 +390,7 @@ static int llc_conn_service(struct sock *sk, struct sk_= buff *skb) struct llc_sock *llc =3D llc_sk(sk); int rc =3D 1; =20 - if (llc->state > NBR_CONN_STATES) + if (!llc_conn_state_in_service(llc->state)) goto out; rc =3D 0; trans =3D llc_qualify_conn_ev(sk, skb); --=20 2.34.1 From nobody Sat Sep 26 11:46:31 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7627286D4D; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; cv=none; b=OMjZ0VqaYnFHYtp2dbo8rIMRflSCIM31D4T99dSDoUvu2X5PdTl+x+NMNo00MBJD0P9g8LImPnPPJFQplVB3Q+53KNZKZ641UEQn90/RbKBl5sr639pFGvyfPkrK59wlynvpdxIMYpwtAg9cglaQ5KqrmnMTbqg+KhPqxEN/cdo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; c=relaxed/simple; bh=1ycvHY9jrwxBEJiElS9KgXcZkwTdzcGWMI9WMUwOVpI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DGJyzXcQsBZp4KlLPKLWAYTxSOZc+s2Wy4+QOPsbmcExl+F4Ch7Nf/yR5SYYenw+V34urXdXXnkx4+ATQ4Or+ZzXFATc1gyIOuhbhr76c2Raz3ScKEEJS/6tG7LZ6ELzlnWgR8iyb+MMdJagKRWJb2DcttKDps+fNxN2bI/tDWo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lyoBuXey; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lyoBuXey" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4C2521F00A3E; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788296588; bh=o4y3Au5yahfVgXckmce7YK44RkvqJlP5g7zCQkT9ths=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=lyoBuXeyZGkvBx5inPWIcsXUqabNkkahs7Qu6n4Y6dPU/G/Y/Zkl2qr9TY+xnVoel EW4B5FP4T7BuKgfMi2GxOqrdLgyH9fWpun+30euwgIqkmLmJXuPkK6OnKixVjA3wAA 0J9G3treuHzt435yhD/vmNSKd29swxZI7Ksmi1pjLNtA01XfwEoT5lqFcLLpg+7hbD cKOmWJh8S1+YFzumfGn4J6btnUPGlP0tAel8Zai8/MfE10WQYyB/zH00Cm6ZdaYFlL 3m0OX3HV4ubaj7FNIQpAfsv0wlE1D+OZKj4ZAqmgyPMFXtz3LiuWDOuQorTWT/UigS rNLZDumwHeORQ== From: Kees Cook To: Jakub Kicinski Cc: Kees Cook , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Xuanqiang Luo , Tim Bird , Zihan Xi , linux-kernel@vger.kernel.org, syzbot+628f93722c08dc5aabe0@syzkaller.appspotmail.com, netdev@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next 2/3] llc: report a closed connection for out of service sockets Date: Tue, 1 Sep 2026 14:03:04 -0700 Message-Id: <20260901210308.1173180-2-kees@kernel.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260901210300.i.590-kees@kernel.org> References: <20260901210300.i.590-kees@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=2459; i=kees@kernel.org; h=from:subject; bh=1ycvHY9jrwxBEJiElS9KgXcZkwTdzcGWMI9WMUwOVpI=; b=owGbwMvMwCVmps19z/KJym7G02pJDFnTbTuWGu9cHBXJv8pmf2fjlcflyZNPLmbWm7u0My5Ls P9N9QXZjlIWBjEuBlkxRZYgO/c4F4+37eHucxVh5rAygQxh4OIUgIkwTWZkeFd/W9V6puyNs163 s5S01aV/zevpEW3dNqkykre+8bB3MCPD0YB/O28vfcwUHnPTi6GI4+2ObTo6NbMCD/b+4Iw7xCv DDQA= X-Developer-Key: i=kees@kernel.org; a=openpgp; fpr=A5C3F68F229DD60F723E6E138972F4DFDC6DC026 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" An unsolicited frame can move a bound PF_LLC socket from LLC_CONN_STATE_ADM to LLC_CONN_OUT_OF_SVC while the socket stays open from userspace's point of view. Both upper layer entry points handle that badly: - llc_establish_connection() has no state check at all, so with the state machine now refusing the event, connect(2) would return the state machine's "1" failure indication as a positive syscall return value. - llc_build_and_send_pkt() special cases LLC_CONN_STATE_ADM as -ECONNABORTED but falls through to -EBUSY for LLC_CONN_OUT_OF_SVC. -EBUSY describes a connection that is momentarily unable to send, not one that no longer exists. Report -ECONNABORTED from both. There is deliberately no Fixes: tag here. The connect(2) return value only becomes observable once the previous patch makes the state machine refuse the event, and the llc_build_and_send_pkt() change is a long-standing errno inaccuracy with no memory safety impact. Backporting this on its own would fix nothing. Build tested ARCH=3Dx86_64 net/llc/ with GCC 14.2.0, CONFIG_LLC2=3Dy and = =3Dm. Assisted-by: Claude:claude-opus-5[1m] Signed-off-by: Kees Cook --- net/llc/llc_if.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/llc/llc_if.c b/net/llc/llc_if.c index 1514362e613d..f1a3f3372c4f 100644 --- a/net/llc/llc_if.c +++ b/net/llc/llc_if.c @@ -41,7 +41,8 @@ int llc_build_and_send_pkt(struct sock *sk, struct sk_buf= f *skb) int rc =3D -ECONNABORTED; struct llc_sock *llc =3D llc_sk(sk); =20 - if (unlikely(llc->state =3D=3D LLC_CONN_STATE_ADM)) + if (unlikely(llc->state =3D=3D LLC_CONN_STATE_ADM || + llc->state =3D=3D LLC_CONN_OUT_OF_SVC)) goto out_free; rc =3D -EBUSY; if (unlikely(llc_data_accept_state(llc->state) || /* data_conn_refuse */ @@ -82,6 +83,15 @@ int llc_establish_connection(struct sock *sk, const u8 *= lmac, u8 *dmac, u8 dsap) struct llc_sock *llc =3D llc_sk(sk); struct sock *existing; =20 + /* + * A socket parked in LLC_CONN_OUT_OF_SVC has no state machine to run, + * so there is nothing to establish. Report it as a closed connection + * rather than handing llc_conn_state_process() an event it can only + * throw away. + */ + if (unlikely(llc->state =3D=3D LLC_CONN_OUT_OF_SVC)) + return -ECONNABORTED; + laddr.lsap =3D llc->sap->laddr.lsap; daddr.lsap =3D dsap; memcpy(daddr.mac, dmac, sizeof(daddr.mac)); --=20 2.34.1 From nobody Sat Sep 26 11:46:31 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A76D5303A04; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; cv=none; b=PYO5SsIwEAuWwbtOXAj+jZuK2EpX73GZcfeo6tSljLJkJGrFCnWr6aZEqR9jXfFrdonjshNH1WqokKLUNbll3KROYXtPLzsuVTyESN3qRhFB9O9vSmiA/yXJjY2hNOxn8VJX5aIDh08WGdfTIM2UshGjZ0gZ90puUcnEPaOExY8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788296590; c=relaxed/simple; bh=HNU76bQ6klmPij1w/3RPifphGIswGZzN/mqmhOZiQ/Q=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Fwm7hpXoN13LUteindCdpBd4CkUFRcdbRRhrDg2Wky9LBmjZ2MV0GiGItLNHaQNeYwIN+xZlQtTuT3DBH9k3fwc03dsJR2qarMzymY2Z5V9ct+cdTdwRvXIdYsJOUfQdt+CGex56LfKXPU3O5Fn48dmU/VP29cLtf/SYILuMzQY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=bQTSaNt+; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="bQTSaNt+" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4A5F31F00A3D; Tue, 1 Sep 2026 21:03:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788296588; bh=FKylBELlw5xNn5/cbFbsdT+XUM4vHMkeZv/IlwgO01s=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=bQTSaNt+MpfqH8AfmbyE7u+FPdMp3OgZtQufn8MPdrYsoEGtQ6Vrne7X0aP/ZpZqk DFBKQFA5/Is5/MCasUNLv5JfabkJvO8iVOtPJ7o0PomMv4jg84SgZnjjQgf3ARd+GE yt/QfeizTkH1HPbf0+cEJ2dpxo7+R/kx0ofjlOo4DrXydfpQz+92zChG6MKjdF7kvS 2j1YGooYpvvaLWadsapJ4oW3RTSmuzp7FIvMidSwDTzJAca0iAyl0PF7Y2lHTM4y+W HcEqD9SkKWkw8TilehKapfI4qFOXkemUElhSa9gwl9bZ+dDTg3Czv0R+SHtXapYcWu /3JcXzAAopQQw== From: Kees Cook To: Jakub Kicinski Cc: Kees Cook , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Xuanqiang Luo , Tim Bird , Zihan Xi , linux-kernel@vger.kernel.org, syzbot+628f93722c08dc5aabe0@syzkaller.appspotmail.com, netdev@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next 3/3] llc: add KUnit tests for the connection state machine bounds Date: Tue, 1 Sep 2026 14:03:05 -0700 Message-Id: <20260901210308.1173180-3-kees@kernel.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260901210300.i.590-kees@kernel.org> References: <20260901210300.i.590-kees@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=7965; i=kees@kernel.org; h=from:subject; bh=HNU76bQ6klmPij1w/3RPifphGIswGZzN/mqmhOZiQ/Q=; b=owGbwMvMwCVmps19z/KJym7G02pJDFnTbTuUV/kG3g3nY+fXEJ87rfrE66eLJ5ZpdTnzM/2X9 lf8kC3YUcrCIMbFICumyBJk5x7n4vG2Pdx9riLMHFYmkCEMXJwCMJGwDQx/OOum5qQffqJ8rHLH r95/AZfM2Dzzsp899Yi+4SSntNG5hJGhvWXXAUHpYxwS84ysJHTSNqwqso6UNDHUKs5WSdXfosk LAA== X-Developer-Key: i=kees@kernel.org; a=openpgp; fpr=A5C3F68F229DD60F723E6E138972F4DFDC6DC026 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a KUnit suite for the LLC type 2 connection state machine. The test file is #included by llc_conn.c so that it can reach the static state machine helpers. llc_conn_state_process_out_of_svc() is the regression test for the syzbot report: it feeds a PF_LLC socket sitting in LLC_CONN_STATE_ADM an I format command PDU with the P bit clear, which matches only the catch-all llc_adm_state_trans_5 transition and parks the socket in LLC_CONN_OUT_OF_SVC, then feeds it a second one. Without the preceding fixes, on a CONFIG_UBSAN_BOUNDS=3Dy plus CONFIG_KASAN=3Dy kernel that case prints both halves of the reported splat and then dies: UBSAN: array-index-out-of-bounds in net/llc/llc_conn.c:712:24 index -1 is out of range for type 'int [12][5]' BUG: KASAN: global-out-of-bounds in llc_conn_state_process The other cases cover the 1-based indexing invariant of llc_conn_state_table[], the bounds of llc_conn_state_in_service(), and an event delivered with a state past the end of the table. $ ./tools/testing/kunit/kunit.py run --arch=3Dx86_64 \ --kconfig_add CONFIG_NET=3Dy --kconfig_add CONFIG_LLC2=3Dy llc2_conn [PASSED] llc_conn_state_table_is_one_based [PASSED] llc_conn_state_in_service_bounds [PASSED] llc_conn_state_process_out_of_svc [PASSED] llc_conn_state_process_bad_state Build tested ARCH=3Dx86_64 net/llc/ with GCC 14.2.0 at CONFIG_LLC2=3Dy and CONFIG_LLC2=3Dm (the suite needs CONFIG_LLC2=3Dy). Tests run 4/4 passing on ARCH=3Dum and on ARCH=3Dx86_64 under qemu, and confirmed to fail with the two preceding patches reverted, both with CONFIG_KASAN=3Dy and CONFIG_UBSAN_BOUNDS=3Dy. Assisted-by: Claude:claude-opus-5[1m] Signed-off-by: Kees Cook --- net/llc/Kconfig | 14 ++++ net/llc/llc_conn.c | 4 + net/llc/tests/conn_kunit.c | 146 +++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 net/llc/tests/conn_kunit.c diff --git a/net/llc/Kconfig b/net/llc/Kconfig index 7f79f5e134f9..19bd101d6329 100644 --- a/net/llc/Kconfig +++ b/net/llc/Kconfig @@ -8,3 +8,17 @@ config LLC2 help This is a Logical Link Layer type 2, connection oriented support. Select this if you want to have support for PF_LLC sockets. + +config LLC2_CONN_KUNIT_TEST + bool "KUnit tests for the LLC type 2 connection state machine" if !KUNIT_= ALL_TESTS + depends on KUNIT=3Dy && LLC2=3Dy + default KUNIT_ALL_TESTS + help + This builds the KUnit tests for the LLC type 2 connection state + machine, covering the bounds of the state transition tables and the + handling of events delivered to an out of service connection. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index a4ae29b42300..07f077a30c30 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -1059,3 +1059,7 @@ void llc_sk_reset(struct sock *sk) llc->failed_data_req =3D 0 ; llc->last_nr =3D 0; } + +#if IS_ENABLED(CONFIG_LLC2_CONN_KUNIT_TEST) +#include "tests/conn_kunit.c" +#endif diff --git a/net/llc/tests/conn_kunit.c b/net/llc/tests/conn_kunit.c new file mode 100644 index 000000000000..86d3f122df0a --- /dev/null +++ b/net/llc/tests/conn_kunit.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the LLC type 2 connection state machine. + * + * This file is #included by llc_conn.c so that the tests can reach the + * static helpers of the state machine. + */ +#include +#include +#include + +/* + * Build the smallest event that reaches the LLC_CONN_STATE_ADM catch-all + * transition: an I format command PDU with the P bit clear. It matches + * neither llc_conn_ev_rx_sabme_cmd_pbit_set_x(), + * llc_conn_ev_rx_disc_cmd_pbit_set_x() nor + * llc_conn_ev_rx_xxx_cmd_pbit_set_1(), so llc_adm_state_trans_5 wins. + */ +static struct sk_buff *llc_conn_test_rx_pdu(struct kunit *test, struct soc= k *sk) +{ + struct llc_conn_state_ev *ev; + struct llc_pdu_sn *pdu; + struct sk_buff *skb; + + skb =3D alloc_skb(sizeof(*pdu), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, skb); + + skb_reset_network_header(skb); + pdu =3D skb_put(skb, sizeof(*pdu)); + pdu->dsap =3D 0x42; + pdu->ssap =3D LLC_PDU_CMD; + pdu->ctrl_1 =3D LLC_PDU_TYPE_I; + pdu->ctrl_2 =3D 0; + + skb->sk =3D sk; + ev =3D llc_conn_ev(skb); + memset(ev, 0, sizeof(*ev)); + ev->type =3D LLC_CONN_EV_TYPE_PDU; + + return skb; +} + +static struct socket *llc_conn_test_socket(struct kunit *test) +{ + struct socket *sock; + int rc; + + rc =3D sock_create_kern(&init_net, PF_LLC, SOCK_DGRAM, 0, &sock); + if (rc) + kunit_skip(test, "cannot create a PF_LLC socket: %d", rc); + + return sock; +} + +/* + * llc_conn_state_table[] and llc_offset_table[] are indexed with "state -= 1", + * which only works while every state is its own 1-based index. + */ +static void llc_conn_state_table_is_one_based(struct kunit *test) +{ + u8 state; + + for (state =3D LLC_CONN_STATE_ADM; state <=3D LLC_CONN_STATE_TEMP; state+= +) + KUNIT_EXPECT_EQ(test, llc_conn_state_table[state - 1].current_state, + state); +} + +static void llc_conn_state_in_service_bounds(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, llc_conn_state_in_service(LLC_CONN_OUT_OF_SVC)); + KUNIT_EXPECT_TRUE(test, llc_conn_state_in_service(LLC_CONN_STATE_ADM)); + KUNIT_EXPECT_TRUE(test, llc_conn_state_in_service(LLC_CONN_STATE_TEMP)); + KUNIT_EXPECT_FALSE(test, llc_conn_state_in_service(LLC_CONN_STATE_TEMP + = 1)); + KUNIT_EXPECT_FALSE(test, llc_conn_state_in_service(U8_MAX)); +} + +/* + * Regression test for the syzbot report below: an unsolicited frame moves= a + * socket sitting in LLC_CONN_STATE_ADM to LLC_CONN_OUT_OF_SVC, and the ne= xt + * frame for the same socket used to index llc_conn_state_table[-1] and + * llc_offset_table[-1][] before it was dropped. + * + * Link: https://lore.kernel.org/all/6a95888b.4d659fcc.734b4.0051.GAE@goog= le.com + */ +static void llc_conn_state_process_out_of_svc(struct kunit *test) +{ + struct sk_buff *first, *second; + struct socket *sock; + struct sock *sk; + + sock =3D llc_conn_test_socket(test); + sk =3D sock->sk; + + first =3D llc_conn_test_rx_pdu(test, sk); + second =3D llc_conn_test_rx_pdu(test, sk); + + lock_sock(sk); + KUNIT_EXPECT_EQ(test, llc_sk(sk)->state, LLC_CONN_STATE_ADM); + + /* The catch-all ADM transition parks the socket out of service. */ + KUNIT_EXPECT_EQ(test, llc_conn_state_process(sk, first), 0); + KUNIT_EXPECT_EQ(test, llc_sk(sk)->state, LLC_CONN_OUT_OF_SVC); + + /* The next event must be refused rather than indexed with -1. */ + KUNIT_EXPECT_NE(test, llc_conn_state_process(sk, second), 0); + KUNIT_EXPECT_EQ(test, llc_sk(sk)->state, LLC_CONN_OUT_OF_SVC); + release_sock(sk); + + sock_release(sock); +} + +/* The same refusal has to cover states past the end of the state table. */ +static void llc_conn_state_process_bad_state(struct kunit *test) +{ + struct socket *sock; + struct sk_buff *skb; + struct sock *sk; + + sock =3D llc_conn_test_socket(test); + sk =3D sock->sk; + + skb =3D llc_conn_test_rx_pdu(test, sk); + + lock_sock(sk); + llc_sk(sk)->state =3D LLC_CONN_STATE_TEMP + 1; + KUNIT_EXPECT_NE(test, llc_conn_state_process(sk, skb), 0); + llc_sk(sk)->state =3D LLC_CONN_STATE_ADM; + release_sock(sk); + + sock_release(sock); +} + +static struct kunit_case llc_conn_test_cases[] =3D { + KUNIT_CASE(llc_conn_state_table_is_one_based), + KUNIT_CASE(llc_conn_state_in_service_bounds), + KUNIT_CASE(llc_conn_state_process_out_of_svc), + KUNIT_CASE(llc_conn_state_process_bad_state), + {} +}; + +static struct kunit_suite llc_conn_test_suite =3D { + .name =3D "llc2_conn", + .test_cases =3D llc_conn_test_cases, +}; + +kunit_test_suite(llc_conn_test_suite); --=20 2.34.1