Netdev List
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: Jakub Kicinski <kuba@kernel.org>
Cc: Kees Cook <kees@kernel.org>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	Xuanqiang Luo <luoxuanqiang@kylinos.cn>,
	Tim Bird <tim.bird@sony.com>, Zihan Xi <zihanx@nebusec.ai>,
	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	[thread overview]
Message-ID: <20260901210308.1173180-3-kees@kernel.org> (raw)
In-Reply-To: <20260901210300.i.590-kees@kernel.org>

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=y plus
CONFIG_KASAN=y 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=x86_64 \
        --kconfig_add CONFIG_NET=y --kconfig_add CONFIG_LLC2=y 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=x86_64 net/llc/ with GCC 14.2.0 at CONFIG_LLC2=y and
CONFIG_LLC2=m (the suite needs CONFIG_LLC2=y). Tests run 4/4 passing on
ARCH=um and on ARCH=x86_64 under qemu, and confirmed to fail with the
two preceding patches reverted, both with CONFIG_KASAN=y and
CONFIG_UBSAN_BOUNDS=y.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Kees Cook <kees@kernel.org>
---
 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=y && LLC2=y
+	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	= 0 ;
 	llc->last_nr		= 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 <kunit/test.h>
+#include <linux/net.h>
+#include <net/net_namespace.h>
+
+/*
+ * 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 sock *sk)
+{
+	struct llc_conn_state_ev *ev;
+	struct llc_pdu_sn *pdu;
+	struct sk_buff *skb;
+
+	skb = alloc_skb(sizeof(*pdu), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, skb);
+
+	skb_reset_network_header(skb);
+	pdu = skb_put(skb, sizeof(*pdu));
+	pdu->dsap   = 0x42;
+	pdu->ssap   = LLC_PDU_CMD;
+	pdu->ctrl_1 = LLC_PDU_TYPE_I;
+	pdu->ctrl_2 = 0;
+
+	skb->sk = sk;
+	ev = llc_conn_ev(skb);
+	memset(ev, 0, sizeof(*ev));
+	ev->type = LLC_CONN_EV_TYPE_PDU;
+
+	return skb;
+}
+
+static struct socket *llc_conn_test_socket(struct kunit *test)
+{
+	struct socket *sock;
+	int rc;
+
+	rc = 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 = LLC_CONN_STATE_ADM; state <= 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 next
+ * 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@google.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 = llc_conn_test_socket(test);
+	sk = sock->sk;
+
+	first = llc_conn_test_rx_pdu(test, sk);
+	second = 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 = llc_conn_test_socket(test);
+	sk = sock->sk;
+
+	skb = llc_conn_test_rx_pdu(test, sk);
+
+	lock_sock(sk);
+	llc_sk(sk)->state = LLC_CONN_STATE_TEMP + 1;
+	KUNIT_EXPECT_NE(test, llc_conn_state_process(sk, skb), 0);
+	llc_sk(sk)->state = LLC_CONN_STATE_ADM;
+	release_sock(sk);
+
+	sock_release(sock);
+}
+
+static struct kunit_case llc_conn_test_cases[] = {
+	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 = {
+	.name = "llc2_conn",
+	.test_cases = llc_conn_test_cases,
+};
+
+kunit_test_suite(llc_conn_test_suite);
-- 
2.34.1


      parent reply	other threads:[~2026-09-01 21:03 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-01 21:03 [PATCH net-next 0/3] llc: do not run the state machine on out of service connections Kees Cook
2026-09-01 21:03 ` [PATCH net-next 1/3] " Kees Cook
2026-09-01 21:03 ` [PATCH net-next 2/3] llc: report a closed connection for out of service sockets Kees Cook
2026-09-01 21:03 ` Kees Cook [this message]

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=20260901210308.1173180-3-kees@kernel.org \
    --to=kees@kernel.org \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luoxuanqiang@kylinos.cn \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=syzbot+628f93722c08dc5aabe0@syzkaller.appspotmail.com \
    --cc=tim.bird@sony.com \
    --cc=zihanx@nebusec.ai \
    /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