| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/02 05:56 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"NETDEVSIM",
"TLS",
"TLS_DEVICE"
],
"FocusSymbols": [
"nsim_tls_init",
"nsim_tls_teardown",
"nsim_do_tls",
"nsim_tls_dev_add",
"nsim_tls_dev_del",
"nsim_tls_dev_resync",
"nsim_tls_dbg_read"
],
"KMSANReasoning": "The patch series implements emulated kTLS offload support for netdevsim. Analysis of memory initialization and handling across the patch shows:\n\n1. Memory allocation and initialization: All dynamic structures (such as `struct nsim_tls_conn` and debugfs read buffers) are allocated via `kzalloc_obj` or `kzalloc`, ensuring all struct fields are zero-initialized. List heads and spinlocks are explicitly initialized in `nsim_tls_init`.\n2. Stack variables: `struct nsim_tls_tuple` is fully populated by `nsim_tls_parse()` before any fields are read or compared in `nsim_tls_rx_offloaded()`.\n3. Userspace data transfer: The debugfs output in `nsim_tls_dbg_read()` uses `scnprintf()` into a zeroed buffer and copies exactly `p - buf` bytes via `simple_read_from_buffer()`.\n4. Packet tag handling: `nsim_tls_tx_zero_tags()` explicitly `memset`s the trailing record tag gap to zero to prevent sending uninitialized bytes across the simulated wire.\n\nPotential memory safety bugs in this driver extension (e.g., skb bounds, RCU lifetime management, concurrency, or list corruption) are detectable by KASAN, LOCKDEP, and standard sanitizers. There is no risk of uninitialized memory usage or info-leaks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch adds emulated kTLS hardware offload support to the netdevsim driver, including TLS device ops (add, del, resync), TX/RX packet handling and zeroing of authentication tags, and debugfs interface. This code runs in virtualized test environments and modifies reachable network device logic worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/02 05:56 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 240c86524d0a06a0245d0c51e355f1d5f954fc6c\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Sep 2 05:56:14 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/Kconfig b/drivers/net/Kconfig\nindex ff79c466712df..cb4d4bb87afc2 100644\n--- a/drivers/net/Kconfig\n+++ b/drivers/net/Kconfig\n@@ -607,6 +607,7 @@ config NETDEVSIM\n \tdepends on INET\n \tdepends on PSAMPLE || PSAMPLE=n\n \tdepends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n\n+\tdepends on TLS || TLS=n\n \tselect NET_DEVLINK\n \tselect PAGE_POOL\n \tselect NET_SHAPER\ndiff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile\nindex 87718204fb4de..c7f7621323b2b 100644\n--- a/drivers/net/netdevsim/Makefile\n+++ b/drivers/net/netdevsim/Makefile\n@@ -25,3 +25,7 @@ endif\n ifneq ($(CONFIG_MACSEC),)\n netdevsim-objs += macsec.o\n endif\n+\n+ifneq ($(CONFIG_TLS_DEVICE),)\n+netdevsim-objs += tls.o\n+endif\ndiff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c\nindex b4a99f3ceac60..b9e0a2f5b18fc 100644\n--- a/drivers/net/netdevsim/netdev.c\n+++ b/drivers/net/netdevsim/netdev.c\n@@ -151,6 +151,8 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)\n \tif (dr)\n \t\tgoto out_drop_free;\n \n+\tnsim_do_tls(skb, ns, peer_ns);\n+\n \trxq = skb_get_queue_mapping(skb);\n \tif (rxq \u003e= peer_dev-\u003enum_rx_queues)\n \t\trxq = rxq % peer_dev-\u003enum_rx_queues;\n@@ -1068,6 +1070,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)\n \n \tnsim_macsec_init(ns);\n \tnsim_ipsec_init(ns);\n+\tnsim_tls_init(ns);\n \n \terr = register_netdevice(ns-\u003enetdev);\n \tif (err)\n@@ -1095,6 +1098,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)\n \tRCU_INIT_POINTER(ns-\u003epeer, NULL);\n \tunregister_netdevice(ns-\u003enetdev);\n err_ipsec_teardown:\n+\tnsim_tls_teardown(ns);\n \tnsim_ipsec_teardown(ns);\n \tnsim_macsec_teardown(ns);\n \tnsim_bpf_uninit(ns);\n@@ -1197,6 +1201,7 @@ void nsim_destroy(struct netdevsim *ns)\n \tRCU_INIT_POINTER(ns-\u003epeer, NULL);\n \tunregister_netdevice(dev);\n \tif (nsim_dev_port_is_pf(ns-\u003ensim_dev_port)) {\n+\t\tnsim_tls_teardown(ns);\n \t\tnsim_macsec_teardown(ns);\n \t\tnsim_ipsec_teardown(ns);\n \t\tnsim_bpf_uninit(ns);\ndiff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h\nindex 55aec41237b9b..b4646c7061bd0 100644\n--- a/drivers/net/netdevsim/netdevsim.h\n+++ b/drivers/net/netdevsim/netdevsim.h\n@@ -56,6 +56,20 @@ struct nsim_ipsec {\n \tu32 tx;\n };\n \n+struct nsim_tls {\n+\tstruct list_head conns;\n+\tspinlock_t lock;\t/* protects conns and the counters below */\n+\tstruct dentry *dfile;\n+\tu32 count;\n+\tu32 tx_conn;\n+\tu32 rx_conn;\n+\tatomic64_t tx_packets;\n+\tatomic64_t tx_bytes;\n+\tatomic64_t rx_packets;\n+\tatomic64_t rx_bytes;\n+\tatomic64_t resyncs;\n+};\n+\n #define NSIM_MACSEC_MAX_SECY_COUNT 3\n #define NSIM_MACSEC_MAX_RXSC_COUNT 1\n struct nsim_rxsc {\n@@ -141,6 +155,7 @@ struct netdevsim {\n \tbool bpf_map_accept;\n \tstruct nsim_ipsec ipsec;\n \tstruct nsim_macsec macsec;\n+\tstruct nsim_tls tls;\n \tstruct nsim_vlan vlan;\n \tstruct {\n \t\tu32 inject_error;\n@@ -434,6 +449,26 @@ static inline bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)\n }\n #endif\n \n+#if IS_ENABLED(CONFIG_TLS_DEVICE)\n+void nsim_tls_init(struct netdevsim *ns);\n+void nsim_tls_teardown(struct netdevsim *ns);\n+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,\n+\t\t struct netdevsim *peer_ns);\n+#else\n+static inline void nsim_tls_init(struct netdevsim *ns)\n+{\n+}\n+\n+static inline void nsim_tls_teardown(struct netdevsim *ns)\n+{\n+}\n+\n+static inline void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,\n+\t\t\t struct netdevsim *peer_ns)\n+{\n+}\n+#endif\n+\n #if IS_ENABLED(CONFIG_MACSEC)\n void nsim_macsec_init(struct netdevsim *ns);\n void nsim_macsec_teardown(struct netdevsim *ns);\ndiff --git a/drivers/net/netdevsim/tls.c b/drivers/net/netdevsim/tls.c\nnew file mode 100644\nindex 0000000000000..079d30a86b5d6\n--- /dev/null\n+++ b/drivers/net/netdevsim/tls.c\n@@ -0,0 +1,482 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003clinux/ipv6.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/tcp.h\u003e\n+#include \u003cnet/ip.h\u003e\n+#include \u003cnet/ipv6.h\u003e\n+#include \u003cnet/tcp.h\u003e\n+#include \u003cnet/tls.h\u003e\n+\n+#include \"netdevsim.h\"\n+\n+/* Emulated kTLS offload. No crypto is performed: TX puts the record on the\n+ * wire as plaintext and RX just marks it decrypted, so both ports have to\n+ * have the offload installed on the connection before any data flows. That\n+ * is the only supported setup. Anything else - a peer with no matching\n+ * context, a context installed mid stream - has the core run the software\n+ * AEAD over plaintext, which fails the connection with -EBADMSG.\n+ */\n+\n+#define NSIM_TLS_MAX_CONN\t32\n+\n+/* netdev_fix_features() drops NETIF_F_HW_TLS_RX unless the device also does\n+ * RX checksums, which every offload capable NIC does.\n+ */\n+#define NSIM_TLS_FEATURES\t(NETIF_F_HW_TLS_TX | NETIF_F_HW_TLS_RX | \\\n+\t\t\t\t NETIF_F_RXCSUM)\n+\n+struct nsim_tls_conn {\n+\tstruct list_head list;\n+\tstruct rcu_head rcu;\n+\n+\t/* Identity as seen on the wire, from the point of view of the port\n+\t * the offload was installed on: l* is this side, r* is the peer.\n+\t */\n+\tstruct in6_addr laddr;\n+\tstruct in6_addr raddr;\n+\t__be16 lport;\n+\t__be16 rport;\n+\tu16 family;\n+\n+\tenum tls_offload_ctx_dir dir;\n+\tu32 start_sn;\n+\tbool started;\n+\tu16 cipher_type;\n+\tconst struct tls_context *tls_ctx;\n+};\n+\n+struct nsim_tls_tuple {\n+\tstruct in6_addr saddr;\n+\tstruct in6_addr daddr;\n+\t__be16 sport;\n+\t__be16 dport;\n+\tu32 seq;\n+};\n+\n+/* Parse the tuple from the frame the way a NIC would. The xmit skb is not\n+ * always a well formed local TCP skb (AF_PACKET, tc redirect); use\n+ * skb_header_pointer() so a non-linear or truncated header cannot be misread.\n+ */\n+static bool nsim_tls_parse(const struct sk_buff *skb,\n+\t\t\t struct nsim_tls_tuple *t)\n+{\n+\tint off = skb_network_offset(skb);\n+\tconst struct tcphdr *th;\n+\tstruct tcphdr _th;\n+\n+\tswitch (skb-\u003eprotocol) {\n+\tcase htons(ETH_P_IP): {\n+\t\tconst struct iphdr *iph;\n+\t\tstruct iphdr _iph;\n+\n+\t\tiph = skb_header_pointer(skb, off, sizeof(_iph), \u0026_iph);\n+\t\tif (!iph || iph-\u003eversion != 4 || iph-\u003eihl \u003c 5 ||\n+\t\t iph-\u003eprotocol != IPPROTO_TCP || ip_is_fragment(iph))\n+\t\t\treturn false;\n+\t\tipv6_addr_set_v4mapped(iph-\u003esaddr, \u0026t-\u003esaddr);\n+\t\tipv6_addr_set_v4mapped(iph-\u003edaddr, \u0026t-\u003edaddr);\n+\t\toff += iph-\u003eihl * 4;\n+\t\tbreak;\n+\t}\n+\tcase htons(ETH_P_IPV6): {\n+\t\tconst struct ipv6hdr *ip6h;\n+\t\tstruct ipv6hdr _ip6h;\n+\t\t__be16 frag_off;\n+\t\tu8 nexthdr;\n+\t\tint thoff;\n+\n+\t\tip6h = skb_header_pointer(skb, off, sizeof(_ip6h), \u0026_ip6h);\n+\t\tif (!ip6h)\n+\t\t\treturn false;\n+\n+\t\tnexthdr = ip6h-\u003enexthdr;\n+\t\tthoff = ipv6_skip_exthdr(skb, off + sizeof(_ip6h), \u0026nexthdr,\n+\t\t\t\t\t \u0026frag_off);\n+\t\tif (thoff \u003c 0 || nexthdr != IPPROTO_TCP || frag_off)\n+\t\t\treturn false;\n+\n+\t\tt-\u003esaddr = ip6h-\u003esaddr;\n+\t\tt-\u003edaddr = ip6h-\u003edaddr;\n+\t\toff = thoff;\n+\t\tbreak;\n+\t}\n+\tdefault:\n+\t\treturn false;\n+\t}\n+\n+\tth = skb_header_pointer(skb, off, sizeof(_th), \u0026_th);\n+\tif (!th)\n+\t\treturn false;\n+\tt-\u003esport = th-\u003esource;\n+\tt-\u003edport = th-\u003edest;\n+\tt-\u003eseq = ntohl(th-\u003eseq);\n+\n+\treturn true;\n+}\n+\n+/* No socket on RX yet, so match the tuple installed at -\u003etls_dev_add(), the\n+ * way the hardware does.\n+ */\n+static bool nsim_tls_rx_offloaded(struct netdevsim *ns,\n+\t\t\t\t const struct nsim_tls_tuple *t)\n+{\n+\tstruct nsim_tls_conn *conn;\n+\n+\tlist_for_each_entry_rcu(conn, \u0026ns-\u003etls.conns, list) {\n+\t\tif (conn-\u003edir != TLS_OFFLOAD_CTX_DIR_RX ||\n+\t\t conn-\u003elport != t-\u003edport || conn-\u003erport != t-\u003esport ||\n+\t\t !ipv6_addr_equal(\u0026conn-\u003eladdr, \u0026t-\u003edaddr) ||\n+\t\t !ipv6_addr_equal(\u0026conn-\u003eraddr, \u0026t-\u003esaddr))\n+\t\t\tcontinue;\n+\n+\t\t/* Anything before start_sn predates the offload and has to\n+\t\t * stay encrypted. Only worth asking once: TCP sequence\n+\t\t * numbers wrap, and a stream that has moved 2G past a\n+\t\t * sequence it is still compared against looks like it went\n+\t\t * backwards.\n+\t\t */\n+\t\tif (!READ_ONCE(conn-\u003estarted)) {\n+\t\t\tif (before(t-\u003eseq, conn-\u003estart_sn))\n+\t\t\t\treturn false;\n+\t\t\tWRITE_ONCE(conn-\u003estarted, true);\n+\t\t}\n+\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+/* The stack leaves an uninitialized, tag sized gap at the tail of each record\n+ * for the device to write the auth tag into. We do not encrypt, so zero it\n+ * to avoid leaking that memory onto the wire. Nothing tells the device where\n+ * the tag is, so walk the records with tls_get_record() and clear the end of\n+ * each one's last frag.\n+ */\n+static void nsim_tls_tx_zero_tags(struct sk_buff *skb)\n+{\n+\tstruct tls_context *ctx = tls_get_ctx(skb-\u003esk);\n+\tconst struct tcphdr *th = tcp_hdr(skb);\n+\tstruct tls_offload_context_tx *tx_ctx;\n+\tu32 seq, end, tag_size;\n+\tunsigned long flags;\n+\tunsigned int off;\n+\n+\toff = skb_transport_offset(skb) + __tcp_hdrlen(th);\n+\tif (off \u003e= skb-\u003elen)\n+\t\treturn;\n+\n+\ttag_size = ctx-\u003eprot_info.tag_size;\n+\tseq = ntohl(th-\u003eseq);\n+\tend = seq + skb-\u003elen - off;\n+\ttx_ctx = tls_offload_ctx_tx(ctx);\n+\n+\tspin_lock_irqsave(\u0026tx_ctx-\u003elock, flags);\n+\twhile (before(seq, end)) {\n+\t\tstruct tls_record_info *record;\n+\t\tskb_frag_t *frag;\n+\t\tu64 rcd_sn;\n+\n+\t\trecord = tls_get_record(tx_ctx, seq, \u0026rcd_sn);\n+\t\tif (!record || tls_record_is_start_marker(record))\n+\t\t\tbreak;\n+\n+\t\tfrag = \u0026record-\u003efrags[record-\u003enum_frags - 1];\n+\t\tmemset(skb_frag_address(frag) + skb_frag_size(frag) - tag_size,\n+\t\t 0, tag_size);\n+\n+\t\tseq = record-\u003eend_seq;\n+\t}\n+\tspin_unlock_irqrestore(\u0026tx_ctx-\u003elock, flags);\n+}\n+\n+/* Stand in for the inline encryption the hardware would do on the way out.\n+ * The stack has already framed the record and we do not encrypt, so the tag\n+ * is all that is left to deal with. The socket is right here, so identify\n+ * the connection the way the real drivers do.\n+ */\n+static bool nsim_tls_tx(struct netdevsim *ns, struct sk_buff *skb)\n+{\n+\tif (!tls_is_skb_tx_device_offloaded(skb))\n+\t\treturn false;\n+\n+\t/* The question tls_validate_xmit_skb() asks: on anything other than\n+\t * the device the context is attached to, the core has already\n+\t * encrypted the record in software, so it is real ciphertext and none\n+\t * of our business.\n+\t */\n+\tif (rcu_dereference_bh(tls_get_ctx(skb-\u003esk)-\u003enetdev) != ns-\u003enetdev)\n+\t\treturn false;\n+\n+\tnsim_tls_tx_zero_tags(skb);\n+\tatomic64_inc(\u0026ns-\u003etls.tx_packets);\n+\tatomic64_add(skb-\u003elen, \u0026ns-\u003etls.tx_bytes);\n+\n+\treturn true;\n+}\n+\n+/* Stand in for the inline decryption the peer's hardware would do, since we\n+ * are about to hand the skb to its stack. We do not decrypt either, so all\n+ * that is needed is the flag that tells the kTLS core the payload is\n+ * already plaintext.\n+ */\n+static void nsim_tls_rx(struct netdevsim *ns, struct sk_buff *skb)\n+{\n+\tstruct nsim_tls_tuple t;\n+\n+\t/* Not every port registers the offload, and one that does not never\n+\t * initializes the list head, so gate on the count. It is the\n+\t * cheaper test anyway.\n+\t */\n+\tif (!READ_ONCE(ns-\u003etls.count))\n+\t\treturn;\n+\n+\tif (!nsim_tls_parse(skb, \u0026t))\n+\t\treturn;\n+\n+\tif (!nsim_tls_rx_offloaded(ns, \u0026t))\n+\t\treturn;\n+\n+\tskb-\u003edecrypted = 1;\n+\tatomic64_inc(\u0026ns-\u003etls.rx_packets);\n+\tatomic64_add(skb-\u003elen, \u0026ns-\u003etls.rx_bytes);\n+}\n+\n+/* netdevsim has no wire: nsim_start_xmit() hands the skb straight to the\n+ * peer's receive path, so this one call site stands in for the hardware of\n+ * both ports. It has to run before nsim_forward_skb() moves the skb over,\n+ * which resets the headers.\n+ */\n+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,\n+\t\t struct netdevsim *peer_ns)\n+{\n+\t/* nsim_do_psp() ran first and may have wrapped the record stream, so\n+\t * this is no longer a plain TLS over TCP packet. No NIC chains the\n+\t * two inline offloads either, so leave it alone.\n+\t */\n+\tif (skb-\u003eencapsulation)\n+\t\treturn;\n+\n+\t/* Only what this port just \"encrypted\" may skip decryption on the\n+\t * peer. Everything else - a software kTLS sender because the TX\n+\t * feature is off, a frame injected on the tuple - really is\n+\t * ciphertext or not TLS at all, and the core has to deal with it.\n+\t */\n+\tif (!nsim_tls_tx(ns, skb))\n+\t\treturn;\n+\n+\tnsim_tls_rx(peer_ns, skb);\n+}\n+\n+static int nsim_tls_dev_add(struct net_device *netdev, struct sock *sk,\n+\t\t\t enum tls_offload_ctx_dir direction,\n+\t\t\t struct tls_crypto_info *crypto_info,\n+\t\t\t u32 start_offload_tcp_sn)\n+{\n+\tstruct netdevsim *ns = netdev_priv(netdev);\n+\tstruct nsim_tls_conn *conn;\n+\tint ret = 0;\n+\n+\t/* Mirror the cipher support of a typical offload capable NIC. */\n+\tswitch (crypto_info-\u003ecipher_type) {\n+\tcase TLS_CIPHER_AES_GCM_128:\n+\tcase TLS_CIPHER_AES_GCM_256:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tconn = kzalloc_obj(*conn);\n+\tif (!conn)\n+\t\treturn -ENOMEM;\n+\n+\tif (sk-\u003esk_family == AF_INET6) {\n+\t\tconn-\u003eladdr = sk-\u003esk_v6_rcv_saddr;\n+\t\tconn-\u003eraddr = sk-\u003esk_v6_daddr;\n+\t} else {\n+\t\tipv6_addr_set_v4mapped(sk-\u003esk_rcv_saddr, \u0026conn-\u003eladdr);\n+\t\tipv6_addr_set_v4mapped(sk-\u003esk_daddr, \u0026conn-\u003eraddr);\n+\t}\n+\tconn-\u003efamily = sk-\u003esk_family;\n+\tconn-\u003elport = htons(inet_sk(sk)-\u003einet_num);\n+\tconn-\u003erport = sk-\u003esk_dport;\n+\tconn-\u003edir = direction;\n+\tconn-\u003estart_sn = start_offload_tcp_sn;\n+\tconn-\u003ecipher_type = crypto_info-\u003ecipher_type;\n+\tconn-\u003etls_ctx = tls_get_ctx(sk);\n+\n+\tspin_lock_bh(\u0026ns-\u003etls.lock);\n+\tif (ns-\u003etls.count \u003e= NSIM_TLS_MAX_CONN) {\n+\t\tret = -ENOSPC;\n+\t\tgoto out_unlock;\n+\t}\n+\tlist_add_tail_rcu(\u0026conn-\u003elist, \u0026ns-\u003etls.conns);\n+\tns-\u003etls.count++;\n+\tif (direction == TLS_OFFLOAD_CTX_DIR_TX)\n+\t\tns-\u003etls.tx_conn++;\n+\telse\n+\t\tns-\u003etls.rx_conn++;\n+out_unlock:\n+\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n+\n+\tif (ret)\n+\t\tkfree(conn);\n+\n+\treturn ret;\n+}\n+\n+static void nsim_tls_dev_del(struct net_device *netdev,\n+\t\t\t struct tls_context *tls_ctx,\n+\t\t\t enum tls_offload_ctx_dir direction)\n+{\n+\tstruct netdevsim *ns = netdev_priv(netdev);\n+\tstruct nsim_tls_conn *conn;\n+\n+\tspin_lock_bh(\u0026ns-\u003etls.lock);\n+\tlist_for_each_entry(conn, \u0026ns-\u003etls.conns, list) {\n+\t\tif (conn-\u003etls_ctx != tls_ctx || conn-\u003edir != direction)\n+\t\t\tcontinue;\n+\n+\t\tlist_del_rcu(\u0026conn-\u003elist);\n+\t\tns-\u003etls.count--;\n+\t\tif (direction == TLS_OFFLOAD_CTX_DIR_TX)\n+\t\t\tns-\u003etls.tx_conn--;\n+\t\telse\n+\t\t\tns-\u003etls.rx_conn--;\n+\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n+\n+\t\tkfree_rcu(conn, rcu);\n+\t\treturn;\n+\t}\n+\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n+\n+\tnetdev_err(netdev, \"TLS %s context not found on del\\n\",\n+\t\t direction == TLS_OFFLOAD_CTX_DIR_TX ? \"tx\" : \"rx\");\n+}\n+\n+/* Nothing is ever out of sync here: the emulation does not decrypt, so it\n+ * never loses the record boundaries and never asks for a resync. The core\n+ * can still call in on the RX path, so account for it and move on.\n+ */\n+static int nsim_tls_dev_resync(struct net_device *netdev, struct sock *sk,\n+\t\t\t u32 seq, u8 *rcd_sn,\n+\t\t\t enum tls_offload_ctx_dir direction)\n+{\n+\tstruct netdevsim *ns = netdev_priv(netdev);\n+\n+\tatomic64_inc(\u0026ns-\u003etls.resyncs);\n+\n+\treturn 0;\n+}\n+\n+static const struct tlsdev_ops nsim_tlsdev_ops = {\n+\t.tls_dev_add\t= nsim_tls_dev_add,\n+\t.tls_dev_del\t= nsim_tls_dev_del,\n+\t.tls_dev_resync\t= nsim_tls_dev_resync,\n+};\n+\n+static ssize_t nsim_tls_dbg_read(struct file *filp, char __user *buffer,\n+\t\t\t\t size_t count, loff_t *ppos)\n+{\n+\tstruct netdevsim *ns = filp-\u003eprivate_data;\n+\tstruct nsim_tls_conn *conn;\n+\tsize_t bufsize;\n+\tchar *buf, *p;\n+\tint len;\n+\n+\t/* Two full IPv6 addresses and ports fit in 160 bytes a line. */\n+\tbufsize = (NSIM_TLS_MAX_CONN * 160) + 200;\n+\tbuf = kzalloc(bufsize, GFP_KERNEL);\n+\tif (!buf)\n+\t\treturn -ENOMEM;\n+\n+\tp = buf;\n+\n+\tspin_lock_bh(\u0026ns-\u003etls.lock);\n+\tp += scnprintf(p, bufsize - (p - buf),\n+\t\t \"conn count=%u tx=%u rx=%u\\n\",\n+\t\t ns-\u003etls.count, ns-\u003etls.tx_conn, ns-\u003etls.rx_conn);\n+\tp += scnprintf(p, bufsize - (p - buf),\n+\t\t \"tx_packets=%llu tx_bytes=%llu rx_packets=%llu rx_bytes=%llu resyncs=%llu\\n\",\n+\t\t atomic64_read(\u0026ns-\u003etls.tx_packets),\n+\t\t atomic64_read(\u0026ns-\u003etls.tx_bytes),\n+\t\t atomic64_read(\u0026ns-\u003etls.rx_packets),\n+\t\t atomic64_read(\u0026ns-\u003etls.rx_bytes),\n+\t\t atomic64_read(\u0026ns-\u003etls.resyncs));\n+\n+\tlist_for_each_entry(conn, \u0026ns-\u003etls.conns, list) {\n+\t\tif (conn-\u003efamily == AF_INET6)\n+\t\t\tp += scnprintf(p, bufsize - (p - buf),\n+\t\t\t\t \"%s [%pI6c]:%u -\u003e [%pI6c]:%u cipher=%u sn=%u\\n\",\n+\t\t\t\t conn-\u003edir == TLS_OFFLOAD_CTX_DIR_TX ?\n+\t\t\t\t \"tx\" : \"rx\",\n+\t\t\t\t \u0026conn-\u003eladdr, ntohs(conn-\u003elport),\n+\t\t\t\t \u0026conn-\u003eraddr, ntohs(conn-\u003erport),\n+\t\t\t\t conn-\u003ecipher_type, conn-\u003estart_sn);\n+\t\telse\n+\t\t\tp += scnprintf(p, bufsize - (p - buf),\n+\t\t\t\t \"%s %pI4:%u -\u003e %pI4:%u cipher=%u sn=%u\\n\",\n+\t\t\t\t conn-\u003edir == TLS_OFFLOAD_CTX_DIR_TX ?\n+\t\t\t\t \"tx\" : \"rx\",\n+\t\t\t\t \u0026conn-\u003eladdr.s6_addr32[3],\n+\t\t\t\t ntohs(conn-\u003elport),\n+\t\t\t\t \u0026conn-\u003eraddr.s6_addr32[3],\n+\t\t\t\t ntohs(conn-\u003erport),\n+\t\t\t\t conn-\u003ecipher_type, conn-\u003estart_sn);\n+\t}\n+\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n+\n+\tlen = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);\n+\n+\tkfree(buf);\n+\n+\treturn len;\n+}\n+\n+static const struct file_operations nsim_tls_dbg_fops = {\n+\t.owner = THIS_MODULE,\n+\t.open = simple_open,\n+\t.read = nsim_tls_dbg_read,\n+\t.llseek = default_llseek,\n+};\n+\n+void nsim_tls_init(struct netdevsim *ns)\n+{\n+\tINIT_LIST_HEAD(\u0026ns-\u003etls.conns);\n+\tspin_lock_init(\u0026ns-\u003etls.lock);\n+\n+\tns-\u003enetdev-\u003etlsdev_ops = \u0026nsim_tlsdev_ops;\n+\tns-\u003enetdev-\u003efeatures |= NSIM_TLS_FEATURES;\n+\tns-\u003enetdev-\u003ehw_features |= NSIM_TLS_FEATURES;\n+\n+\tns-\u003etls.dfile = debugfs_create_file(\"tls\", 0400,\n+\t\t\t\t\t ns-\u003ensim_dev_port-\u003eddir, ns,\n+\t\t\t\t\t \u0026nsim_tls_dbg_fops);\n+}\n+\n+void nsim_tls_teardown(struct netdevsim *ns)\n+{\n+\tstruct nsim_tls_conn *conn, *tmp;\n+\tu32 left;\n+\n+\tdebugfs_remove_recursive(ns-\u003etls.dfile);\n+\n+\tspin_lock_bh(\u0026ns-\u003etls.lock);\n+\tleft = ns-\u003etls.count;\n+\tlist_for_each_entry_safe(conn, tmp, \u0026ns-\u003etls.conns, list) {\n+\t\tlist_del_rcu(\u0026conn-\u003elist);\n+\t\tkfree_rcu(conn, rcu);\n+\t}\n+\tns-\u003etls.count = 0;\n+\tns-\u003etls.tx_conn = 0;\n+\tns-\u003etls.rx_conn = 0;\n+\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n+\n+\tif (left)\n+\t\tnetdev_err(ns-\u003enetdev,\n+\t\t\t \"tearing down TLS offload with %u connections left\\n\",\n+\t\t\t left);\n+}\ndiff --git a/tools/testing/selftests/drivers/net/netdevsim/.gitignore b/tools/testing/selftests/drivers/net/netdevsim/.gitignore\nnew file mode 100644\nindex 0000000000000..9d4f57cb2baa9\n--- /dev/null\n+++ b/tools/testing/selftests/drivers/net/netdevsim/.gitignore\n@@ -0,0 +1,2 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+tls_offload\ndiff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools/testing/selftests/drivers/net/netdevsim/Makefile\nindex 9808c2fbae9e8..5beb4ed64eb52 100644\n--- a/tools/testing/selftests/drivers/net/netdevsim/Makefile\n+++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile\n@@ -1,5 +1,7 @@\n # SPDX-License-Identifier: GPL-2.0+ OR MIT\n \n+CFLAGS += $(KHDR_INCLUDES)\n+\n TEST_PROGS := \\\n \tdevlink.sh \\\n \tdevlink_in_netns.sh \\\n@@ -15,9 +17,14 @@ TEST_PROGS := \\\n \tpeer.sh \\\n \tpsample.sh \\\n \ttc-mq-visibility.sh \\\n+\ttls.sh \\\n \tudp_tunnel_nic.sh \\\n # end of TEST_PROGS\n \n+TEST_GEN_FILES := \\\n+\ttls_offload\n+# end of TEST_GEN_FILES\n+\n TEST_FILES := \\\n \tethtool-common.sh\n # end of TEST_FILES\ndiff --git a/tools/testing/selftests/drivers/net/netdevsim/config b/tools/testing/selftests/drivers/net/netdevsim/config\nindex 5117c78ddf0af..5893111ba1363 100644\n--- a/tools/testing/selftests/drivers/net/netdevsim/config\n+++ b/tools/testing/selftests/drivers/net/netdevsim/config\n@@ -8,4 +8,6 @@ CONFIG_NET_SCH_MULTIQ=y\n CONFIG_NET_SCH_PRIO=y\n CONFIG_PSAMPLE=y\n CONFIG_PTP_1588_CLOCK_MOCK=y\n+CONFIG_TLS=m\n+CONFIG_TLS_DEVICE=y\n CONFIG_VXLAN=m\ndiff --git a/tools/testing/selftests/drivers/net/netdevsim/tls.sh b/tools/testing/selftests/drivers/net/netdevsim/tls.sh\nnew file mode 100755\nindex 0000000000000..59b7d1c0e5148\n--- /dev/null\n+++ b/tools/testing/selftests/drivers/net/netdevsim/tls.sh\n@@ -0,0 +1,334 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# Exercise netdevsim's emulated kTLS device offload over a linked\n+# netdevsim pair, one port per network namespace.\n+#\n+# shellcheck disable=SC2154 # ksft_skip comes from lib.sh\n+\n+lib_dir=$(dirname \"$0\")\n+# shellcheck source=./../../../net/lib.sh\n+# shellcheck disable=SC1091\n+source \"$lib_dir\"/../../../net/lib.sh\n+\n+# Device IDs and the nssv/nscl namespaces are picked the same way as peer.sh.\n+NSIM_DEV_1_ID=$((256 + RANDOM % 256))\n+NSIM_DEV_1_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_1_ID\n+NSIM_DEV_2_ID=$((512 + RANDOM % 256))\n+NSIM_DEV_2_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_2_ID\n+\n+NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device\n+NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device\n+NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device\n+\n+DEBUGFS=/sys/kernel/debug/netdevsim\n+NSIM_DEV_1_TLS=$DEBUGFS/netdevsim$NSIM_DEV_1_ID/ports/0/tls\n+NSIM_DEV_2_TLS=$DEBUGFS/netdevsim$NSIM_DEV_2_ID/ports/0/tls\n+\n+SRV_IP=192.168.13.1\n+CLI_IP=192.168.13.2\n+PORT=4433\n+\n+SYNCDIR=\n+BIN=$lib_dir/tls_offload\n+\n+# The helpers allow themselves 20s to connect and 20s to meet at the barrier.\n+READY_TIMEOUT_SEC=30\n+\n+num_pass=0\n+num_fail=0\n+\n+check()\n+{\n+\tlocal msg=\"$1\"\n+\tlocal ret=\"$2\"\n+\n+\tif [ \"$ret\" -eq 0 ]; then\n+\t\techo \"PASS: $msg\"\n+\t\tnum_pass=$((num_pass + 1))\n+\telse\n+\t\techo \"FAIL: $msg\"\n+\t\tnum_fail=$((num_fail + 1))\n+\tfi\n+}\n+\n+# defer takes a command, not a redirection.\n+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope\n+nsim_dev_del()\n+{\n+\techo \"$1\" \u003e \"$NSIM_DEV_SYS_DEL\"\n+}\n+\n+# Already reaped on the normal path, so ignore both failures.\n+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope\n+kill_wait()\n+{\n+\tkill \"$1\" 2\u003e/dev/null\n+\twait \"$1\" 2\u003e/dev/null\n+\treturn 0\n+}\n+\n+# Each resource is handed to defer only once it exists, so a run that loses a\n+# race for one of the global names does not tear down the winner's.\n+setup()\n+{\n+\tset -e\n+\n+\techo \"$NSIM_DEV_1_ID\" \u003e \"$NSIM_DEV_SYS_NEW\"\n+\tdefer nsim_dev_del \"$NSIM_DEV_1_ID\"\n+\techo \"$NSIM_DEV_2_ID\" \u003e \"$NSIM_DEV_SYS_NEW\"\n+\tdefer nsim_dev_del \"$NSIM_DEV_2_ID\"\n+\tudevadm settle 2\u003e/dev/null || sleep 1\n+\n+\tNSIM_DEV_1_NAME=$(find \"$NSIM_DEV_1_SYS\"/net -maxdepth 1 -type d ! \\\n+\t\t-path \"$NSIM_DEV_1_SYS\"/net -exec basename {} \\;)\n+\tNSIM_DEV_2_NAME=$(find \"$NSIM_DEV_2_SYS\"/net -maxdepth 1 -type d ! \\\n+\t\t-path \"$NSIM_DEV_2_SYS\"/net -exec basename {} \\;)\n+\n+\tip netns add nssv\n+\tdefer ip netns del nssv\n+\tip netns add nscl\n+\tdefer ip netns del nscl\n+\n+\tip link set \"$NSIM_DEV_1_NAME\" netns nssv\n+\tip link set \"$NSIM_DEV_2_NAME\" netns nscl\n+\n+\tip netns exec nssv ip addr add \"$SRV_IP/24\" dev \"$NSIM_DEV_1_NAME\"\n+\tip netns exec nscl ip addr add \"$CLI_IP/24\" dev \"$NSIM_DEV_2_NAME\"\n+\n+\tip netns exec nssv ip link set dev \"$NSIM_DEV_1_NAME\" up\n+\tip netns exec nscl ip link set dev \"$NSIM_DEV_2_NAME\" up\n+\n+\tNSIM_DEV_1_FD=$((256 + RANDOM % 256))\n+\texec {NSIM_DEV_1_FD}\u003c/var/run/netns/nssv\n+\tNSIM_DEV_1_IFIDX=$(ip netns exec nssv \\\n+\t\tcat /sys/class/net/\"$NSIM_DEV_1_NAME\"/ifindex)\n+\n+\tNSIM_DEV_2_FD=$((256 + RANDOM % 256))\n+\texec {NSIM_DEV_2_FD}\u003c/var/run/netns/nscl\n+\tNSIM_DEV_2_IFIDX=$(ip netns exec nscl \\\n+\t\tcat /sys/class/net/\"$NSIM_DEV_2_NAME\"/ifindex)\n+\n+\techo \"$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX\" \\\n+\t\t\u003e \"$NSIM_DEV_SYS_LINK\"\n+\n+\tSYNCDIR=$(mktemp -d)\n+\tdefer rm -rf \"$SYNCDIR\"\n+\tset +e\n+}\n+\n+feature()\n+{\n+\tlocal netns=\"$1\"\n+\tlocal dev=\"$2\"\n+\tlocal feat=\"$3\"\n+\n+\tip netns exec \"$netns\" ethtool -k \"$dev\" 2\u003e/dev/null | \\\n+\t\tsed -n \"s/^$feat: \\([a-z]*\\).*/\\1/p\"\n+}\n+\n+dbg_field()\n+{\n+\tsed -n \"s/.*\\\u003c$2=\\([0-9]*\\).*/\\1/p\" \"$1\" | head -1\n+}\n+\n+tls_stat()\n+{\n+\tip netns exec \"$1\" cat /proc/net/tls_stat | \\\n+\t\tawk -v name=\"$2\" '$1 == name {print $2}'\n+}\n+\n+# shellcheck disable=SC2317,SC2329 # invoked by name from slowwait\n+both_ready()\n+{\n+\t[ -e \"$SYNCDIR/server.ready\" ] \u0026\u0026 [ -e \"$SYNCDIR/client.ready\" ]\n+}\n+\n+# Both ends park once their offload is installed and before any data is\n+# sent, so the driver's context count can be sampled without racing the\n+# transfer. Pass \"nosample\" when the offload is expected to be refused,\n+# since then neither end ever reaches the barrier.\n+run_pair()\n+{\n+\tlocal sample=\"${1:-sample}\"\n+\tlocal srv_rc cli_rc ready=1\n+\n+\trm -f \"$SYNCDIR\"/*.ready \"$SYNCDIR\"/go\n+\tCONNS_1=0\n+\tCONNS_2=0\n+\n+\tdefer_scope_push\n+\n+\tip netns exec nssv \"$BIN\" server \"$SRV_IP\" \"$PORT\" \"$SYNCDIR\" \u0026\n+\tlocal srv_pid=$!\n+\tdefer kill_wait \"$srv_pid\"\n+\tip netns exec nscl \"$BIN\" client \"$SRV_IP\" \"$PORT\" \"$SYNCDIR\" \u0026\n+\tlocal cli_pid=$!\n+\tdefer kill_wait \"$cli_pid\"\n+\n+\tif [ \"$sample\" = \"sample\" ]; then\n+\t\t# Comfortably longer than the helpers take to get to the\n+\t\t# barrier. Timing out here means a sample would be stale, so\n+\t\t# fail the run rather than assert on it.\n+\t\tslowwait \"$READY_TIMEOUT_SEC\" both_ready \u003e/dev/null || ready=0\n+\t\tif [ \"$ready\" -eq 1 ]; then\n+\t\t\tCONNS_1=$(dbg_field \"$NSIM_DEV_1_TLS\" count)\n+\t\t\tCONNS_2=$(dbg_field \"$NSIM_DEV_2_TLS\" count)\n+\t\t\t: \"${CONNS_1:=0}\"\n+\t\t\t: \"${CONNS_2:=0}\"\n+\t\tfi\n+\tfi\n+\ttouch \"$SYNCDIR/go\"\n+\n+\twait \"$srv_pid\"; srv_rc=$?\n+\twait \"$cli_pid\"; cli_rc=$?\n+\n+\tdefer_scope_pop\n+\n+\t[ \"$ready\" -eq 1 ] \u0026\u0026 [ \"$srv_rc\" -eq 0 ] \u0026\u0026 [ \"$cli_rc\" -eq 0 ]\n+}\n+\n+###\n+### Code start\n+###\n+\n+if [ \"$(id -u)\" -ne 0 ]; then\n+\techo \"SKIP: need root\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+if ! command -v ethtool \u003e/dev/null; then\n+\techo \"SKIP: ethtool not found\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+if [ ! -x \"$BIN\" ]; then\n+\techo \"SKIP: $BIN not built\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+modprobe netdevsim 2\u003e/dev/null\n+if [ ! -d /sys/bus/netdevsim ]; then\n+\techo \"SKIP: netdevsim not available\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+modprobe tls 2\u003e/dev/null\n+if [ ! -e /proc/net/tls_stat ]; then\n+\techo \"SKIP: kernel TLS not available\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+trap defer_scopes_cleanup EXIT\n+setup\n+\n+# /proc/net/tls_stat only proves generic kTLS. Without CONFIG_TLS_DEVICE the\n+# netdevsim TLS hooks are stubbed out: no offload features and no per-port tls\n+# debugfs file. That is an unsupported configuration for this test, not a\n+# failure - skip it.\n+if [ ! -e \"$NSIM_DEV_1_TLS\" ]; then\n+\techo \"SKIP: netdevsim built without TLS device offload (CONFIG_TLS_DEVICE=n)\"\n+\texit \"$ksft_skip\"\n+fi\n+\n+# The offload has to be advertised, and on by default like the other\n+# netdevsim crypto offloads.\n+for f in tls-hw-tx-offload tls-hw-rx-offload; do\n+\t[ \"$(feature nssv \"$NSIM_DEV_1_NAME\" \"$f\")\" = \"on\" ]\n+\tcheck \"$f advertised and on by default\" $?\n+done\n+\n+[ -e \"$NSIM_DEV_1_TLS\" ]\n+check \"per-port debugfs tls file exists\" $?\n+\n+# Main data path run.\n+run_pair\n+check \"offloaded TLS data transfer\" $?\n+\n+# Sampled at the barrier, so each port must be holding exactly the TX and\n+# the RX context of its own socket.\n+[ \"$CONNS_1\" -eq 2 ] \u0026\u0026 [ \"$CONNS_2\" -eq 2 ]\n+check \"tx and rx contexts installed on both ports\" $?\n+\n+# Both ends must have gone through the device path, not the SW fallback.\n+[ \"$(tls_stat nssv TlsTxDevice)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nssv TlsRxDevice)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nscl TlsTxDevice)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nscl TlsRxDevice)\" -ge 1 ]\n+check \"both ends used the device path\" $?\n+\n+[ \"$(tls_stat nssv TlsTxSw)\" -eq 0 ] \u0026\u0026 [ \"$(tls_stat nscl TlsTxSw)\" -eq 0 ]\n+check \"no silent fallback to the software path\" $?\n+\n+[ \"$(tls_stat nssv TlsDecryptError)\" -eq 0 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nscl TlsDecryptError)\" -eq 0 ]\n+check \"no decrypt errors\" $?\n+\n+# The driver must have seen the records go by in both directions.\n+[ \"$(dbg_field \"$NSIM_DEV_1_TLS\" tx_packets)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(dbg_field \"$NSIM_DEV_1_TLS\" rx_packets)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(dbg_field \"$NSIM_DEV_2_TLS\" tx_packets)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(dbg_field \"$NSIM_DEV_2_TLS\" rx_packets)\" -ge 1 ]\n+check \"driver counted offloaded packets both ways\" $?\n+\n+# Sockets are closed by now, so every context must have been given back.\n+# TX contexts are torn down from a workqueue after the socket is destroyed\n+# (tls_device_sk_destruct() -\u003e tls_device_tx_del_task), so tls_dev_del() may\n+# not have run yet - poll rather than sample once.\n+# shellcheck disable=SC2317,SC2329 # invoked by name from busywait\n+all_ctx_released()\n+{\n+\t[ \"$(dbg_field \"$NSIM_DEV_1_TLS\" count)\" -eq 0 ] \u0026\u0026\n+\t\t[ \"$(dbg_field \"$NSIM_DEV_2_TLS\" count)\" -eq 0 ]\n+}\n+\n+busywait 2000 all_ctx_released \u003e/dev/null\n+check \"all offload contexts released on close\" $?\n+\n+[ \"$(tls_stat nssv TlsCurrTxDevice)\" -eq 0 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nssv TlsCurrRxDevice)\" -eq 0 ]\n+check \"no device contexts left behind\" $?\n+\n+set_features()\n+{\n+\tlocal want=\"$1\"\n+\tlocal ret=0\n+\tlocal f\n+\n+\tfor f in tls-hw-tx-offload tls-hw-rx-offload; do\n+\t\tip netns exec nssv ethtool -K \"$NSIM_DEV_1_NAME\" \"$f\" \"$want\"\n+\t\tip netns exec nscl ethtool -K \"$NSIM_DEV_2_NAME\" \"$f\" \"$want\"\n+\n+\t\t[ \"$(feature nssv \"$NSIM_DEV_1_NAME\" \"$f\")\" = \"$want\" ] || ret=1\n+\t\t[ \"$(feature nscl \"$NSIM_DEV_2_NAME\" \"$f\")\" = \"$want\" ] || ret=1\n+\tdone\n+\n+\treturn \"$ret\"\n+}\n+\n+# Turning the features off has to make the offload refuse the connection;\n+# the test binary insists on the device path, so it must now fail.\n+set_features off\n+check \"both offload features turned off on both ports\" $?\n+\n+run_pair nosample\n+rc=$?\n+[ \"$rc\" -ne 0 ]\n+check \"offload declined once the feature is off\" $?\n+\n+# Both directions on both ends have to have landed on the software path.\n+[ \"$(tls_stat nssv TlsTxSw)\" -ge 1 ] \u0026\u0026 [ \"$(tls_stat nssv TlsRxSw)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nscl TlsTxSw)\" -ge 1 ] \u0026\u0026 \\\n+\t[ \"$(tls_stat nscl TlsRxSw)\" -ge 1 ]\n+check \"software path used when offload is off\" $?\n+\n+set_features on\n+check \"both offload features turned back on\" $?\n+\n+run_pair\n+check \"offload works again after re-enabling\" $?\n+\n+echo\n+echo \"passed: $num_pass failed: $num_fail\"\n+[ \"$num_fail\" -eq 0 ] \u0026\u0026 exit 0\n+exit 1\ndiff --git a/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c\nnew file mode 100644\nindex 0000000000000..0d7c8950ca6ce\n--- /dev/null\n+++ b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c\n@@ -0,0 +1,505 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * kTLS device offload data path exercise, driven by tls.sh.\n+ *\n+ * One instance runs as the server and one as the client, each in its own\n+ * network namespace, connected back to back by a linked netdevsim pair.\n+ * Both ends enable kTLS and rely on netdevsim's emulated TLS offload, so\n+ * every record travels through net/tls/tls_device.c rather than the\n+ * software path.\n+ *\n+ * The two processes rendezvous through a shared directory so that neither\n+ * side sends before the other has installed its RX offload.\n+ */\n+\n+#define _GNU_SOURCE\n+\n+#include \u003carpa/inet.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cnetinet/in.h\u003e\n+#include \u003cnetinet/tcp.h\u003e\n+#include \u003cstdarg.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/socket.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003ctime.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \u003clinux/tls.h\u003e\n+\n+#ifndef SOL_TLS\n+#define SOL_TLS\t\t\t282\n+#endif\n+\n+#ifndef TCP_ULP\n+#define TCP_ULP\t\t\t31\n+#endif\n+\n+#define BULK_LEN\t\t(200 * 1024)\n+#define MORE_FRAGS\t\t64\n+#define SPLICE_FRAG_LEN\t\t4096\n+#define SPLICE_FRAGS\t\t8\n+\n+/* TLS_MIN_RECORD_SIZE_LIM and TLS_MAX_PAYLOAD_SIZE, which are not uapi. */\n+#define REC_LIM_MIN\t\t64\n+#define REC_LIM_MAX\t\t16384\n+\n+#define SMALL_RECS\t\t100\n+#define SMALL_LEN\t\t(REC_LIM_MIN * SMALL_RECS)\n+\n+#define SYNC_TIMEOUT_MS\t\t20000\n+#define CONNECT_TIMEOUT_MS\t20000\n+\n+/* accept() and the transfers have no timeout of their own, and tls.sh waits\n+ * on both helpers. Bound the whole run so a stuck peer cannot hang the test.\n+ */\n+#define RUN_TIMEOUT_SEC\t\t120\n+\n+static const char *role;\n+\n+static void die(const char *what)\n+{\n+\tfprintf(stderr, \"%s: %s: %s\\n\", role, what, strerror(errno));\n+\texit(1);\n+}\n+\n+static void fail(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tfprintf(stderr, \"%s: \", role);\n+\tva_start(ap, fmt);\n+\tvfprintf(stderr, fmt, ap);\n+\tva_end(ap);\n+\tfprintf(stderr, \"\\n\");\n+\texit(1);\n+}\n+\n+static void msleep(unsigned int ms)\n+{\n+\tstruct timespec ts = {\n+\t\t.tv_sec = ms / 1000,\n+\t\t.tv_nsec = (ms % 1000) * 1000000L,\n+\t};\n+\n+\tnanosleep(\u0026ts, NULL);\n+}\n+\n+/* /proc/net/tls_stat is per netns, so both ends can check that their own\n+ * connection really landed on the device path.\n+ */\n+static unsigned long read_tls_stat(const char *name)\n+{\n+\tchar line[256];\n+\tunsigned long val;\n+\tFILE *f;\n+\n+\tf = fopen(\"/proc/net/tls_stat\", \"r\");\n+\tif (!f)\n+\t\tdie(\"open /proc/net/tls_stat\");\n+\n+\twhile (fgets(line, sizeof(line), f)) {\n+\t\tchar key[64];\n+\n+\t\tif (sscanf(line, \"%63s %lu\", key, \u0026val) != 2)\n+\t\t\tcontinue;\n+\t\tif (!strcmp(key, name)) {\n+\t\t\tfclose(f);\n+\t\t\treturn val;\n+\t\t}\n+\t}\n+\n+\tfclose(f);\n+\tfail(\"%s not found in /proc/net/tls_stat\", name);\n+\treturn 0;\n+}\n+\n+static void fill_pattern(char *buf, size_t len, unsigned int seed)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c len; i++)\n+\t\tbuf[i] = (char)(seed + i * 31 + (i \u003e\u003e 8) * 7);\n+}\n+\n+static void check_pattern(const char *buf, size_t len, unsigned int seed,\n+\t\t\t const char *what)\n+{\n+\tchar *want = malloc(len);\n+\tsize_t i;\n+\n+\tif (!want)\n+\t\tdie(\"malloc\");\n+\n+\tfill_pattern(want, len, seed);\n+\tfor (i = 0; i \u003c len; i++) {\n+\t\tif (buf[i] != want[i])\n+\t\t\tfail(\"%s: payload mismatch at byte %zu: got 0x%02x want 0x%02x\",\n+\t\t\t what, i, (unsigned char)buf[i],\n+\t\t\t (unsigned char)want[i]);\n+\t}\n+\n+\tfree(want);\n+}\n+\n+static void write_all(int fd, const char *buf, size_t len)\n+{\n+\tsize_t done = 0;\n+\n+\twhile (done \u003c len) {\n+\t\tssize_t n = send(fd, buf + done, len - done, 0);\n+\n+\t\tif (n \u003c 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tdie(\"send\");\n+\t\t}\n+\t\tdone += n;\n+\t}\n+}\n+\n+static void read_all(int fd, char *buf, size_t len)\n+{\n+\tsize_t done = 0;\n+\n+\twhile (done \u003c len) {\n+\t\tssize_t n = recv(fd, buf + done, len - done, 0);\n+\n+\t\tif (n \u003c 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tdie(\"recv\");\n+\t\t}\n+\t\tif (n == 0)\n+\t\t\tfail(\"peer closed after %zu of %zu bytes\", done, len);\n+\t\tdone += n;\n+\t}\n+}\n+\n+static void enable_ktls(int fd)\n+{\n+\tstruct tls12_crypto_info_aes_gcm_128 ci = {};\n+\tunsigned long tx_before, rx_before;\n+\n+\ttx_before = read_tls_stat(\"TlsTxDevice\");\n+\trx_before = read_tls_stat(\"TlsRxDevice\");\n+\n+\tif (setsockopt(fd, IPPROTO_TCP, TCP_ULP, \"tls\", sizeof(\"tls\")))\n+\t\tdie(\"setsockopt(TCP_ULP, tls)\");\n+\n+\tci.info.version = TLS_1_2_VERSION;\n+\tci.info.cipher_type = TLS_CIPHER_AES_GCM_128;\n+\tmemset(ci.iv, 'i', sizeof(ci.iv));\n+\tmemset(ci.key, 'k', sizeof(ci.key));\n+\tmemset(ci.salt, 's', sizeof(ci.salt));\n+\tmemset(ci.rec_seq, 0, sizeof(ci.rec_seq));\n+\n+\tif (setsockopt(fd, SOL_TLS, TLS_TX, \u0026ci, sizeof(ci)))\n+\t\tdie(\"setsockopt(TLS_TX)\");\n+\tif (setsockopt(fd, SOL_TLS, TLS_RX, \u0026ci, sizeof(ci)))\n+\t\tdie(\"setsockopt(TLS_RX)\");\n+\n+\t/* The whole point of the exercise: refuse to silently fall back to\n+\t * the software path, otherwise the test would pass without ever\n+\t * touching tls_device.c.\n+\t */\n+\tif (read_tls_stat(\"TlsTxDevice\") != tx_before + 1)\n+\t\tfail(\"TX did not land on the device path (TlsTxDevice %lu -\u003e %lu)\",\n+\t\t tx_before, read_tls_stat(\"TlsTxDevice\"));\n+\tif (read_tls_stat(\"TlsRxDevice\") != rx_before + 1)\n+\t\tfail(\"RX did not land on the device path (TlsRxDevice %lu -\u003e %lu)\",\n+\t\t rx_before, read_tls_stat(\"TlsRxDevice\"));\n+}\n+\n+static void sync_path(char *out, size_t len, const char *dir, const char *who)\n+{\n+\tif ((size_t)snprintf(out, len, \"%s/%s.ready\", dir, who) \u003e= len)\n+\t\tfail(\"sync dir path too long\");\n+}\n+\n+static void rendezvous(const char *dir, const char *me, const char *peer)\n+{\n+\tchar mine[PATH_MAX], theirs[PATH_MAX];\n+\tunsigned int waited = 0;\n+\tint fd;\n+\n+\tsync_path(mine, sizeof(mine), dir, me);\n+\tsync_path(theirs, sizeof(theirs), dir, peer);\n+\n+\tfd = open(mine, O_CREAT | O_WRONLY, 0600);\n+\tif (fd \u003c 0)\n+\t\tdie(\"create sync file\");\n+\tclose(fd);\n+\n+\twhile (access(theirs, F_OK)) {\n+\t\tif (waited \u003e= SYNC_TIMEOUT_MS)\n+\t\t\tfail(\"timed out waiting for %s\", peer);\n+\t\tmsleep(20);\n+\t\twaited += 20;\n+\t}\n+}\n+\n+/* Both ends stop here with their offload installed and no data sent yet,\n+ * so that the driver state can be inspected from the outside.\n+ */\n+static void wait_for_go(const char *dir)\n+{\n+\tunsigned int waited = 0;\n+\tchar go[PATH_MAX];\n+\n+\tif ((size_t)snprintf(go, sizeof(go), \"%s/go\", dir) \u003e= sizeof(go))\n+\t\tfail(\"sync dir path too long\");\n+\n+\twhile (access(go, F_OK)) {\n+\t\tif (waited \u003e= SYNC_TIMEOUT_MS)\n+\t\t\tfail(\"timed out waiting for go\");\n+\t\tmsleep(20);\n+\t\twaited += 20;\n+\t}\n+}\n+\n+/* Small writes with MSG_MORE accumulate into one open record before it is\n+ * pushed, which is the interesting part of tls_push_data().\n+ */\n+static void send_msg_more(int fd, unsigned int seed)\n+{\n+\tchar buf[MORE_FRAGS + 1];\n+\tint i;\n+\n+\tfill_pattern(buf, sizeof(buf), seed);\n+\n+\tfor (i = 0; i \u003c MORE_FRAGS; i++) {\n+\t\tif (send(fd, buf + i, 1, MSG_MORE) != 1)\n+\t\t\tdie(\"send(MSG_MORE)\");\n+\t}\n+\tif (send(fd, buf + MORE_FRAGS, 1, 0) != 1)\n+\t\tdie(\"send(last)\");\n+}\n+\n+/* splice() reaches tls_push_data() with MSG_SPLICE_PAGES once\n+ * TLS_TX_ZEROCOPY_RO is enabled, which is a distinct fragment path.\n+ */\n+static void send_splice(int fd, unsigned int seed)\n+{\n+\tchar buf[SPLICE_FRAG_LEN];\n+\tint val = 1;\n+\tint i;\n+\n+\tif (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, \u0026val, sizeof(val)))\n+\t\tdie(\"setsockopt(TLS_TX_ZEROCOPY_RO)\");\n+\n+\tfor (i = 0; i \u003c SPLICE_FRAGS; i++) {\n+\t\tint p[2];\n+\n+\t\tfill_pattern(buf, sizeof(buf), seed + i * SPLICE_FRAG_LEN);\n+\n+\t\tif (pipe(p))\n+\t\t\tdie(\"pipe\");\n+\t\tif (write(p[1], buf, sizeof(buf)) != sizeof(buf))\n+\t\t\tdie(\"write to pipe\");\n+\t\tif (splice(p[0], NULL, fd, NULL, sizeof(buf),\n+\t\t\t i == SPLICE_FRAGS - 1 ? 0 : SPLICE_F_MORE) !=\n+\t\t sizeof(buf))\n+\t\t\tdie(\"splice\");\n+\t\tclose(p[0]);\n+\t\tclose(p[1]);\n+\t}\n+\n+\tval = 0;\n+\tif (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, \u0026val, sizeof(val)))\n+\t\tdie(\"setsockopt(TLS_TX_ZEROCOPY_RO off)\");\n+}\n+\n+/* A record can be up to 16K, so normally one segment carries a piece of a\n+ * single record. Shrinking the limit puts a dozen or so whole records in\n+ * every segment instead, which is the multi-record path through the driver.\n+ */\n+static void send_small_records(int fd, unsigned int seed)\n+{\n+\tchar buf[SMALL_LEN];\n+\tuint16_t limit;\n+\n+\tlimit = REC_LIM_MIN;\n+\tif (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, \u0026limit,\n+\t\t sizeof(limit)))\n+\t\tdie(\"setsockopt(TLS_TX_MAX_PAYLOAD_LEN)\");\n+\n+\tfill_pattern(buf, sizeof(buf), seed);\n+\twrite_all(fd, buf, sizeof(buf));\n+\n+\tlimit = REC_LIM_MAX;\n+\tif (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, \u0026limit,\n+\t\t sizeof(limit)))\n+\t\tdie(\"setsockopt(TLS_TX_MAX_PAYLOAD_LEN restore)\");\n+}\n+\n+#define SEED_C2S_BULK\t0x11\n+#define SEED_S2C_BULK\t0x22\n+#define SEED_C2S_MORE\t0x33\n+#define SEED_C2S_SPLICE\t0x44\n+#define SEED_C2S_SMALL\t0x55\n+\n+static void run_client(int fd)\n+{\n+\tchar *buf = malloc(BULK_LEN);\n+\n+\tif (!buf)\n+\t\tdie(\"malloc\");\n+\n+\tfill_pattern(buf, BULK_LEN, SEED_C2S_BULK);\n+\twrite_all(fd, buf, BULK_LEN);\n+\n+\tread_all(fd, buf, BULK_LEN);\n+\tcheck_pattern(buf, BULK_LEN, SEED_S2C_BULK, \"server -\u003e client bulk\");\n+\n+\tsend_msg_more(fd, SEED_C2S_MORE);\n+\tsend_splice(fd, SEED_C2S_SPLICE);\n+\tsend_small_records(fd, SEED_C2S_SMALL);\n+\n+\t/* Wait for the server's verdict before tearing anything down. */\n+\tread_all(fd, buf, 1);\n+\tif (buf[0] != 'k')\n+\t\tfail(\"server reported a failure\");\n+\n+\tfree(buf);\n+}\n+\n+static void run_server(int fd)\n+{\n+\tsize_t splice_len = (size_t)SPLICE_FRAG_LEN * SPLICE_FRAGS;\n+\tchar *buf = malloc(BULK_LEN);\n+\tchar more[MORE_FRAGS + 1];\n+\tchar *sbuf;\n+\tchar ok = 'k';\n+\tint i;\n+\n+\tsbuf = malloc(splice_len);\n+\tif (!buf || !sbuf)\n+\t\tdie(\"malloc\");\n+\n+\tread_all(fd, buf, BULK_LEN);\n+\tcheck_pattern(buf, BULK_LEN, SEED_C2S_BULK, \"client -\u003e server bulk\");\n+\n+\tfill_pattern(buf, BULK_LEN, SEED_S2C_BULK);\n+\twrite_all(fd, buf, BULK_LEN);\n+\n+\tread_all(fd, more, sizeof(more));\n+\tcheck_pattern(more, sizeof(more), SEED_C2S_MORE, \"client -\u003e server MSG_MORE\");\n+\n+\tread_all(fd, sbuf, splice_len);\n+\tfor (i = 0; i \u003c SPLICE_FRAGS; i++)\n+\t\tcheck_pattern(sbuf + (size_t)i * SPLICE_FRAG_LEN,\n+\t\t\t SPLICE_FRAG_LEN, SEED_C2S_SPLICE +\n+\t\t\t i * SPLICE_FRAG_LEN, \"client -\u003e server splice\");\n+\n+\tread_all(fd, buf, SMALL_LEN);\n+\tcheck_pattern(buf, SMALL_LEN, SEED_C2S_SMALL,\n+\t\t \"client -\u003e server small records\");\n+\n+\twrite_all(fd, \u0026ok, 1);\n+\n+\tfree(sbuf);\n+\tfree(buf);\n+}\n+\n+static int do_server(const char *ip, int port, const char *syncdir)\n+{\n+\tstruct sockaddr_in sa = {};\n+\tint lfd, fd, one = 1;\n+\n+\tlfd = socket(AF_INET, SOCK_STREAM, 0);\n+\tif (lfd \u003c 0)\n+\t\tdie(\"socket\");\n+\tif (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, \u0026one, sizeof(one)))\n+\t\tdie(\"SO_REUSEADDR\");\n+\n+\tsa.sin_family = AF_INET;\n+\tsa.sin_port = htons(port);\n+\tif (inet_pton(AF_INET, ip, \u0026sa.sin_addr) != 1)\n+\t\tfail(\"bad bind address %s\", ip);\n+\n+\tif (bind(lfd, (struct sockaddr *)\u0026sa, sizeof(sa)))\n+\t\tdie(\"bind\");\n+\tif (listen(lfd, 1))\n+\t\tdie(\"listen\");\n+\n+\tfd = accept(lfd, NULL, NULL);\n+\tif (fd \u003c 0)\n+\t\tdie(\"accept\");\n+\tclose(lfd);\n+\n+\tenable_ktls(fd);\n+\trendezvous(syncdir, \"server\", \"client\");\n+\twait_for_go(syncdir);\n+\n+\trun_server(fd);\n+\n+\tclose(fd);\n+\treturn 0;\n+}\n+\n+static int do_client(const char *ip, int port, const char *syncdir)\n+{\n+\tstruct sockaddr_in sa = {};\n+\tunsigned int waited = 0;\n+\tint fd;\n+\n+\tsa.sin_family = AF_INET;\n+\tsa.sin_port = htons(port);\n+\tif (inet_pton(AF_INET, ip, \u0026sa.sin_addr) != 1)\n+\t\tfail(\"bad server address %s\", ip);\n+\n+\tfor (;;) {\n+\t\tfd = socket(AF_INET, SOCK_STREAM, 0);\n+\t\tif (fd \u003c 0)\n+\t\t\tdie(\"socket\");\n+\t\tif (!connect(fd, (struct sockaddr *)\u0026sa, sizeof(sa)))\n+\t\t\tbreak;\n+\t\tclose(fd);\n+\t\tif (waited \u003e= CONNECT_TIMEOUT_MS)\n+\t\t\tdie(\"connect\");\n+\t\tmsleep(20);\n+\t\twaited += 20;\n+\t}\n+\n+\tenable_ktls(fd);\n+\trendezvous(syncdir, \"client\", \"server\");\n+\twait_for_go(syncdir);\n+\n+\trun_client(fd);\n+\n+\tclose(fd);\n+\treturn 0;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint port;\n+\n+\tif (argc != 5) {\n+\t\tfprintf(stderr,\n+\t\t\t\"usage: %s server|client \u003cip\u003e \u003cport\u003e \u003csyncdir\u003e\\n\",\n+\t\t\targv[0]);\n+\t\treturn 2;\n+\t}\n+\n+\trole = argv[1];\n+\tport = atoi(argv[3]);\n+\n+\talarm(RUN_TIMEOUT_SEC);\n+\n+\tif (!strcmp(role, \"server\"))\n+\t\treturn do_server(argv[2], port, argv[4]);\n+\tif (!strcmp(role, \"client\"))\n+\t\treturn do_client(argv[2], port, argv[4]);\n+\n+\tfprintf(stderr, \"unknown role %s\\n\", role);\n+\treturn 2;\n+}\n"
} |
| 2/1 | 2026/09/02 05:56 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"NETDEVSIM",
"TLS",
"TLS_DEVICE"
],
"FocusSymbols": [
"nsim_tls_init",
"nsim_tls_teardown",
"nsim_do_tls",
"nsim_tls_dev_add",
"nsim_tls_dev_del",
"nsim_tls_dev_resync",
"nsim_tls_dbg_read"
],
"Reasoning": "The patch adds emulated kTLS hardware offload support to the netdevsim driver, including TLS device ops (add, del, resync), TX/RX packet handling and zeroing of authentication tags, and debugfs interface. This code runs in virtualized test environments and modifies reachable network device logic worth fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 240c86524d0a06a0245d0c51e355f1d5f954fc6c
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 2 05:56:14 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ff79c466712df..cb4d4bb87afc2 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -607,6 +607,7 @@ config NETDEVSIM
depends on INET
depends on PSAMPLE || PSAMPLE=n
depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
+ depends on TLS || TLS=n
select NET_DEVLINK
select PAGE_POOL
select NET_SHAPER
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile
index 87718204fb4de..c7f7621323b2b 100644
--- a/drivers/net/netdevsim/Makefile
+++ b/drivers/net/netdevsim/Makefile
@@ -25,3 +25,7 @@ endif
ifneq ($(CONFIG_MACSEC),)
netdevsim-objs += macsec.o
endif
+
+ifneq ($(CONFIG_TLS_DEVICE),)
+netdevsim-objs += tls.o
+endif
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index b4a99f3ceac60..b9e0a2f5b18fc 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -151,6 +151,8 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
if (dr)
goto out_drop_free;
+ nsim_do_tls(skb, ns, peer_ns);
+
rxq = skb_get_queue_mapping(skb);
if (rxq >= peer_dev->num_rx_queues)
rxq = rxq % peer_dev->num_rx_queues;
@@ -1068,6 +1070,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
nsim_macsec_init(ns);
nsim_ipsec_init(ns);
+ nsim_tls_init(ns);
err = register_netdevice(ns->netdev);
if (err)
@@ -1095,6 +1098,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
RCU_INIT_POINTER(ns->peer, NULL);
unregister_netdevice(ns->netdev);
err_ipsec_teardown:
+ nsim_tls_teardown(ns);
nsim_ipsec_teardown(ns);
nsim_macsec_teardown(ns);
nsim_bpf_uninit(ns);
@@ -1197,6 +1201,7 @@ void nsim_destroy(struct netdevsim *ns)
RCU_INIT_POINTER(ns->peer, NULL);
unregister_netdevice(dev);
if (nsim_dev_port_is_pf(ns->nsim_dev_port)) {
+ nsim_tls_teardown(ns);
nsim_macsec_teardown(ns);
nsim_ipsec_teardown(ns);
nsim_bpf_uninit(ns);
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index 55aec41237b9b..b4646c7061bd0 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -56,6 +56,20 @@ struct nsim_ipsec {
u32 tx;
};
+struct nsim_tls {
+ struct list_head conns;
+ spinlock_t lock; /* protects conns and the counters below */
+ struct dentry *dfile;
+ u32 count;
+ u32 tx_conn;
+ u32 rx_conn;
+ atomic64_t tx_packets;
+ atomic64_t tx_bytes;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t resyncs;
+};
+
#define NSIM_MACSEC_MAX_SECY_COUNT 3
#define NSIM_MACSEC_MAX_RXSC_COUNT 1
struct nsim_rxsc {
@@ -141,6 +155,7 @@ struct netdevsim {
bool bpf_map_accept;
struct nsim_ipsec ipsec;
struct nsim_macsec macsec;
+ struct nsim_tls tls;
struct nsim_vlan vlan;
struct {
u32 inject_error;
@@ -434,6 +449,26 @@ static inline bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)
}
#endif
+#if IS_ENABLED(CONFIG_TLS_DEVICE)
+void nsim_tls_init(struct netdevsim *ns);
+void nsim_tls_teardown(struct netdevsim *ns);
+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns);
+#else
+static inline void nsim_tls_init(struct netdevsim *ns)
+{
+}
+
+static inline void nsim_tls_teardown(struct netdevsim *ns)
+{
+}
+
+static inline void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns)
+{
+}
+#endif
+
#if IS_ENABLED(CONFIG_MACSEC)
void nsim_macsec_init(struct netdevsim *ns);
void nsim_macsec_teardown(struct netdevsim *ns);
diff --git a/drivers/net/netdevsim/tls.c b/drivers/net/netdevsim/tls.c
new file mode 100644
index 0000000000000..079d30a86b5d6
--- /dev/null
+++ b/drivers/net/netdevsim/tls.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/ipv6.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/tcp.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/tcp.h>
+#include <net/tls.h>
+
+#include "netdevsim.h"
+
+/* Emulated kTLS offload. No crypto is performed: TX puts the record on the
+ * wire as plaintext and RX just marks it decrypted, so both ports have to
+ * have the offload installed on the connection before any data flows. That
+ * is the only supported setup. Anything else - a peer with no matching
+ * context, a context installed mid stream - has the core run the software
+ * AEAD over plaintext, which fails the connection with -EBADMSG.
+ */
+
+#define NSIM_TLS_MAX_CONN 32
+
+/* netdev_fix_features() drops NETIF_F_HW_TLS_RX unless the device also does
+ * RX checksums, which every offload capable NIC does.
+ */
+#define NSIM_TLS_FEATURES (NETIF_F_HW_TLS_TX | NETIF_F_HW_TLS_RX | \
+ NETIF_F_RXCSUM)
+
+struct nsim_tls_conn {
+ struct list_head list;
+ struct rcu_head rcu;
+
+ /* Identity as seen on the wire, from the point of view of the port
+ * the offload was installed on: l* is this side, r* is the peer.
+ */
+ struct in6_addr laddr;
+ struct in6_addr raddr;
+ __be16 lport;
+ __be16 rport;
+ u16 family;
+
+ enum tls_offload_ctx_dir dir;
+ u32 start_sn;
+ bool started;
+ u16 cipher_type;
+ const struct tls_context *tls_ctx;
+};
+
+struct nsim_tls_tuple {
+ struct in6_addr saddr;
+ struct in6_addr daddr;
+ __be16 sport;
+ __be16 dport;
+ u32 seq;
+};
+
+/* Parse the tuple from the frame the way a NIC would. The xmit skb is not
+ * always a well formed local TCP skb (AF_PACKET, tc redirect); use
+ * skb_header_pointer() so a non-linear or truncated header cannot be misread.
+ */
+static bool nsim_tls_parse(const struct sk_buff *skb,
+ struct nsim_tls_tuple *t)
+{
+ int off = skb_network_offset(skb);
+ const struct tcphdr *th;
+ struct tcphdr _th;
+
+ switch (skb->protocol) {
+ case htons(ETH_P_IP): {
+ const struct iphdr *iph;
+ struct iphdr _iph;
+
+ iph = skb_header_pointer(skb, off, sizeof(_iph), &_iph);
+ if (!iph || iph->version != 4 || iph->ihl < 5 ||
+ iph->protocol != IPPROTO_TCP || ip_is_fragment(iph))
+ return false;
+ ipv6_addr_set_v4mapped(iph->saddr, &t->saddr);
+ ipv6_addr_set_v4mapped(iph->daddr, &t->daddr);
+ off += iph->ihl * 4;
+ break;
+ }
+ case htons(ETH_P_IPV6): {
+ const struct ipv6hdr *ip6h;
+ struct ipv6hdr _ip6h;
+ __be16 frag_off;
+ u8 nexthdr;
+ int thoff;
+
+ ip6h = skb_header_pointer(skb, off, sizeof(_ip6h), &_ip6h);
+ if (!ip6h)
+ return false;
+
+ nexthdr = ip6h->nexthdr;
+ thoff = ipv6_skip_exthdr(skb, off + sizeof(_ip6h), &nexthdr,
+ &frag_off);
+ if (thoff < 0 || nexthdr != IPPROTO_TCP || frag_off)
+ return false;
+
+ t->saddr = ip6h->saddr;
+ t->daddr = ip6h->daddr;
+ off = thoff;
+ break;
+ }
+ default:
+ return false;
+ }
+
+ th = skb_header_pointer(skb, off, sizeof(_th), &_th);
+ if (!th)
+ return false;
+ t->sport = th->source;
+ t->dport = th->dest;
+ t->seq = ntohl(th->seq);
+
+ return true;
+}
+
+/* No socket on RX yet, so match the tuple installed at ->tls_dev_add(), the
+ * way the hardware does.
+ */
+static bool nsim_tls_rx_offloaded(struct netdevsim *ns,
+ const struct nsim_tls_tuple *t)
+{
+ struct nsim_tls_conn *conn;
+
+ list_for_each_entry_rcu(conn, &ns->tls.conns, list) {
+ if (conn->dir != TLS_OFFLOAD_CTX_DIR_RX ||
+ conn->lport != t->dport || conn->rport != t->sport ||
+ !ipv6_addr_equal(&conn->laddr, &t->daddr) ||
+ !ipv6_addr_equal(&conn->raddr, &t->saddr))
+ continue;
+
+ /* Anything before start_sn predates the offload and has to
+ * stay encrypted. Only worth asking once: TCP sequence
+ * numbers wrap, and a stream that has moved 2G past a
+ * sequence it is still compared against looks like it went
+ * backwards.
+ */
+ if (!READ_ONCE(conn->started)) {
+ if (before(t->seq, conn->start_sn))
+ return false;
+ WRITE_ONCE(conn->started, true);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/* The stack leaves an uninitialized, tag sized gap at the tail of each record
+ * for the device to write the auth tag into. We do not encrypt, so zero it
+ * to avoid leaking that memory onto the wire. Nothing tells the device where
+ * the tag is, so walk the records with tls_get_record() and clear the end of
+ * each one's last frag.
+ */
+static void nsim_tls_tx_zero_tags(struct sk_buff *skb)
+{
+ struct tls_context *ctx = tls_get_ctx(skb->sk);
+ const struct tcphdr *th = tcp_hdr(skb);
+ struct tls_offload_context_tx *tx_ctx;
+ u32 seq, end, tag_size;
+ unsigned long flags;
+ unsigned int off;
+
+ off = skb_transport_offset(skb) + __tcp_hdrlen(th);
+ if (off >= skb->len)
+ return;
+
+ tag_size = ctx->prot_info.tag_size;
+ seq = ntohl(th->seq);
+ end = seq + skb->len - off;
+ tx_ctx = tls_offload_ctx_tx(ctx);
+
+ spin_lock_irqsave(&tx_ctx->lock, flags);
+ while (before(seq, end)) {
+ struct tls_record_info *record;
+ skb_frag_t *frag;
+ u64 rcd_sn;
+
+ record = tls_get_record(tx_ctx, seq, &rcd_sn);
+ if (!record || tls_record_is_start_marker(record))
+ break;
+
+ frag = &record->frags[record->num_frags - 1];
+ memset(skb_frag_address(frag) + skb_frag_size(frag) - tag_size,
+ 0, tag_size);
+
+ seq = record->end_seq;
+ }
+ spin_unlock_irqrestore(&tx_ctx->lock, flags);
+}
+
+/* Stand in for the inline encryption the hardware would do on the way out.
+ * The stack has already framed the record and we do not encrypt, so the tag
+ * is all that is left to deal with. The socket is right here, so identify
+ * the connection the way the real drivers do.
+ */
+static bool nsim_tls_tx(struct netdevsim *ns, struct sk_buff *skb)
+{
+ if (!tls_is_skb_tx_device_offloaded(skb))
+ return false;
+
+ /* The question tls_validate_xmit_skb() asks: on anything other than
+ * the device the context is attached to, the core has already
+ * encrypted the record in software, so it is real ciphertext and none
+ * of our business.
+ */
+ if (rcu_dereference_bh(tls_get_ctx(skb->sk)->netdev) != ns->netdev)
+ return false;
+
+ nsim_tls_tx_zero_tags(skb);
+ atomic64_inc(&ns->tls.tx_packets);
+ atomic64_add(skb->len, &ns->tls.tx_bytes);
+
+ return true;
+}
+
+/* Stand in for the inline decryption the peer's hardware would do, since we
+ * are about to hand the skb to its stack. We do not decrypt either, so all
+ * that is needed is the flag that tells the kTLS core the payload is
+ * already plaintext.
+ */
+static void nsim_tls_rx(struct netdevsim *ns, struct sk_buff *skb)
+{
+ struct nsim_tls_tuple t;
+
+ /* Not every port registers the offload, and one that does not never
+ * initializes the list head, so gate on the count. It is the
+ * cheaper test anyway.
+ */
+ if (!READ_ONCE(ns->tls.count))
+ return;
+
+ if (!nsim_tls_parse(skb, &t))
+ return;
+
+ if (!nsim_tls_rx_offloaded(ns, &t))
+ return;
+
+ skb->decrypted = 1;
+ atomic64_inc(&ns->tls.rx_packets);
+ atomic64_add(skb->len, &ns->tls.rx_bytes);
+}
+
+/* netdevsim has no wire: nsim_start_xmit() hands the skb straight to the
+ * peer's receive path, so this one call site stands in for the hardware of
+ * both ports. It has to run before nsim_forward_skb() moves the skb over,
+ * which resets the headers.
+ */
+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns)
+{
+ /* nsim_do_psp() ran first and may have wrapped the record stream, so
+ * this is no longer a plain TLS over TCP packet. No NIC chains the
+ * two inline offloads either, so leave it alone.
+ */
+ if (skb->encapsulation)
+ return;
+
+ /* Only what this port just "encrypted" may skip decryption on the
+ * peer. Everything else - a software kTLS sender because the TX
+ * feature is off, a frame injected on the tuple - really is
+ * ciphertext or not TLS at all, and the core has to deal with it.
+ */
+ if (!nsim_tls_tx(ns, skb))
+ return;
+
+ nsim_tls_rx(peer_ns, skb);
+}
+
+static int nsim_tls_dev_add(struct net_device *netdev, struct sock *sk,
+ enum tls_offload_ctx_dir direction,
+ struct tls_crypto_info *crypto_info,
+ u32 start_offload_tcp_sn)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+ struct nsim_tls_conn *conn;
+ int ret = 0;
+
+ /* Mirror the cipher support of a typical offload capable NIC. */
+ switch (crypto_info->cipher_type) {
+ case TLS_CIPHER_AES_GCM_128:
+ case TLS_CIPHER_AES_GCM_256:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ conn = kzalloc_obj(*conn);
+ if (!conn)
+ return -ENOMEM;
+
+ if (sk->sk_family == AF_INET6) {
+ conn->laddr = sk->sk_v6_rcv_saddr;
+ conn->raddr = sk->sk_v6_daddr;
+ } else {
+ ipv6_addr_set_v4mapped(sk->sk_rcv_saddr, &conn->laddr);
+ ipv6_addr_set_v4mapped(sk->sk_daddr, &conn->raddr);
+ }
+ conn->family = sk->sk_family;
+ conn->lport = htons(inet_sk(sk)->inet_num);
+ conn->rport = sk->sk_dport;
+ conn->dir = direction;
+ conn->start_sn = start_offload_tcp_sn;
+ conn->cipher_type = crypto_info->cipher_type;
+ conn->tls_ctx = tls_get_ctx(sk);
+
+ spin_lock_bh(&ns->tls.lock);
+ if (ns->tls.count >= NSIM_TLS_MAX_CONN) {
+ ret = -ENOSPC;
+ goto out_unlock;
+ }
+ list_add_tail_rcu(&conn->list, &ns->tls.conns);
+ ns->tls.count++;
+ if (direction == TLS_OFFLOAD_CTX_DIR_TX)
+ ns->tls.tx_conn++;
+ else
+ ns->tls.rx_conn++;
+out_unlock:
+ spin_unlock_bh(&ns->tls.lock);
+
+ if (ret)
+ kfree(conn);
+
+ return ret;
+}
+
+static void nsim_tls_dev_del(struct net_device *netdev,
+ struct tls_context *tls_ctx,
+ enum tls_offload_ctx_dir direction)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+ struct nsim_tls_conn *conn;
+
+ spin_lock_bh(&ns->tls.lock);
+ list_for_each_entry(conn, &ns->tls.conns, list) {
+ if (conn->tls_ctx != tls_ctx || conn->dir != direction)
+ continue;
+
+ list_del_rcu(&conn->list);
+ ns->tls.count--;
+ if (direction == TLS_OFFLOAD_CTX_DIR_TX)
+ ns->tls.tx_conn--;
+ else
+ ns->tls.rx_conn--;
+ spin_unlock_bh(&ns->tls.lock);
+
+ kfree_rcu(conn, rcu);
+ return;
+ }
+ spin_unlock_bh(&ns->tls.lock);
+
+ netdev_err(netdev, "TLS %s context not found on del\n",
+ direction == TLS_OFFLOAD_CTX_DIR_TX ? "tx" : "rx");
+}
+
+/* Nothing is ever out of sync here: the emulation does not decrypt, so it
+ * never loses the record boundaries and never asks for a resync. The core
+ * can still call in on the RX path, so account for it and move on.
+ */
+static int nsim_tls_dev_resync(struct net_device *netdev, struct sock *sk,
+ u32 seq, u8 *rcd_sn,
+ enum tls_offload_ctx_dir direction)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+
+ atomic64_inc(&ns->tls.resyncs);
+
+ return 0;
+}
+
+static const struct tlsdev_ops nsim_tlsdev_ops = {
+ .tls_dev_add = nsim_tls_dev_add,
+ .tls_dev_del = nsim_tls_dev_del,
+ .tls_dev_resync = nsim_tls_dev_resync,
+};
+
+static ssize_t nsim_tls_dbg_read(struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct netdevsim *ns = filp->private_data;
+ struct nsim_tls_conn *conn;
+ size_t bufsize;
+ char *buf, *p;
+ int len;
+
+ /* Two full IPv6 addresses and ports fit in 160 bytes a line. */
+ bufsize = (NSIM_TLS_MAX_CONN * 160) + 200;
+ buf = kzalloc(bufsize, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ p = buf;
+
+ spin_lock_bh(&ns->tls.lock);
+ p += scnprintf(p, bufsize - (p - buf),
+ "conn count=%u tx=%u rx=%u\n",
+ ns->tls.count, ns->tls.tx_conn, ns->tls.rx_conn);
+ p += scnprintf(p, bufsize - (p - buf),
+ "tx_packets=%llu tx_bytes=%llu rx_packets=%llu rx_bytes=%llu resyncs=%llu\n",
+ atomic64_read(&ns->tls.tx_packets),
+ atomic64_read(&ns->tls.tx_bytes),
+ atomic64_read(&ns->tls.rx_packets),
+ atomic64_read(&ns->tls.rx_bytes),
+ atomic64_read(&ns->tls.resyncs));
+
+ list_for_each_entry(conn, &ns->tls.conns, list) {
+ if (conn->family == AF_INET6)
+ p += scnprintf(p, bufsize - (p - buf),
+ "%s [%pI6c]:%u -> [%pI6c]:%u cipher=%u sn=%u\n",
+ conn->dir == TLS_OFFLOAD_CTX_DIR_TX ?
+ "tx" : "rx",
+ &conn->laddr, ntohs(conn->lport),
+ &conn->raddr, ntohs(conn->rport),
+ conn->cipher_type, conn->start_sn);
+ else
+ p += scnprintf(p, bufsize - (p - buf),
+ "%s %pI4:%u -> %pI4:%u cipher=%u sn=%u\n",
+ conn->dir == TLS_OFFLOAD_CTX_DIR_TX ?
+ "tx" : "rx",
+ &conn->laddr.s6_addr32[3],
+ ntohs(conn->lport),
+ &conn->raddr.s6_addr32[3],
+ ntohs(conn->rport),
+ conn->cipher_type, conn->start_sn);
+ }
+ spin_unlock_bh(&ns->tls.lock);
+
+ len = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);
+
+ kfree(buf);
+
+ return len;
+}
+
+static const struct file_operations nsim_tls_dbg_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = nsim_tls_dbg_read,
+ .llseek = default_llseek,
+};
+
+void nsim_tls_init(struct netdevsim *ns)
+{
+ INIT_LIST_HEAD(&ns->tls.conns);
+ spin_lock_init(&ns->tls.lock);
+
+ ns->netdev->tlsdev_ops = &nsim_tlsdev_ops;
+ ns->netdev->features |= NSIM_TLS_FEATURES;
+ ns->netdev->hw_features |= NSIM_TLS_FEATURES;
+
+ ns->tls.dfile = debugfs_create_file("tls", 0400,
+ ns->nsim_dev_port->ddir, ns,
+ &nsim_tls_dbg_fops);
+}
+
+void nsim_tls_teardown(struct netdevsim *ns)
+{
+ struct nsim_tls_conn *conn, *tmp;
+ u32 left;
+
+ debugfs_remove_recursive(ns->tls.dfile);
+
+ spin_lock_bh(&ns->tls.lock);
+ left = ns->tls.count;
+ list_for_each_entry_safe(conn, tmp, &ns->tls.conns, list) {
+ list_del_rcu(&conn->list);
+ kfree_rcu(conn, rcu);
+ }
+ ns->tls.count = 0;
+ ns->tls.tx_conn = 0;
+ ns->tls.rx_conn = 0;
+ spin_unlock_bh(&ns->tls.lock);
+
+ if (left)
+ netdev_err(ns->netdev,
+ "tearing down TLS offload with %u connections left\n",
+ left);
+}
diff --git a/tools/testing/selftests/drivers/net/netdevsim/.gitignore b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
new file mode 100644
index 0000000000000..9d4f57cb2baa9
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+tls_offload
diff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools/testing/selftests/drivers/net/netdevsim/Makefile
index 9808c2fbae9e8..5beb4ed64eb52 100644
--- a/tools/testing/selftests/drivers/net/netdevsim/Makefile
+++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0+ OR MIT
+CFLAGS += $(KHDR_INCLUDES)
+
TEST_PROGS := \
devlink.sh \
devlink_in_netns.sh \
@@ -15,9 +17,14 @@ TEST_PROGS := \
peer.sh \
psample.sh \
tc-mq-visibility.sh \
+ tls.sh \
udp_tunnel_nic.sh \
# end of TEST_PROGS
+TEST_GEN_FILES := \
+ tls_offload
+# end of TEST_GEN_FILES
+
TEST_FILES := \
ethtool-common.sh
# end of TEST_FILES
diff --git a/tools/testing/selftests/drivers/net/netdevsim/config b/tools/testing/selftests/drivers/net/netdevsim/config
index 5117c78ddf0af..5893111ba1363 100644
--- a/tools/testing/selftests/drivers/net/netdevsim/config
+++ b/tools/testing/selftests/drivers/net/netdevsim/config
@@ -8,4 +8,6 @@ CONFIG_NET_SCH_MULTIQ=y
CONFIG_NET_SCH_PRIO=y
CONFIG_PSAMPLE=y
CONFIG_PTP_1588_CLOCK_MOCK=y
+CONFIG_TLS=m
+CONFIG_TLS_DEVICE=y
CONFIG_VXLAN=m
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls.sh b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
new file mode 100755
index 0000000000000..59b7d1c0e5148
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
@@ -0,0 +1,334 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Exercise netdevsim's emulated kTLS device offload over a linked
+# netdevsim pair, one port per network namespace.
+#
+# shellcheck disable=SC2154 # ksft_skip comes from lib.sh
+
+lib_dir=$(dirname "$0")
+# shellcheck source=./../../../net/lib.sh
+# shellcheck disable=SC1091
+source "$lib_dir"/../../../net/lib.sh
+
+# Device IDs and the nssv/nscl namespaces are picked the same way as peer.sh.
+NSIM_DEV_1_ID=$((256 + RANDOM % 256))
+NSIM_DEV_1_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_1_ID
+NSIM_DEV_2_ID=$((512 + RANDOM % 256))
+NSIM_DEV_2_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_2_ID
+
+NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device
+NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device
+NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device
+
+DEBUGFS=/sys/kernel/debug/netdevsim
+NSIM_DEV_1_TLS=$DEBUGFS/netdevsim$NSIM_DEV_1_ID/ports/0/tls
+NSIM_DEV_2_TLS=$DEBUGFS/netdevsim$NSIM_DEV_2_ID/ports/0/tls
+
+SRV_IP=192.168.13.1
+CLI_IP=192.168.13.2
+PORT=4433
+
+SYNCDIR=
+BIN=$lib_dir/tls_offload
+
+# The helpers allow themselves 20s to connect and 20s to meet at the barrier.
+READY_TIMEOUT_SEC=30
+
+num_pass=0
+num_fail=0
+
+check()
+{
+ local msg="$1"
+ local ret="$2"
+
+ if [ "$ret" -eq 0 ]; then
+ echo "PASS: $msg"
+ num_pass=$((num_pass + 1))
+ else
+ echo "FAIL: $msg"
+ num_fail=$((num_fail + 1))
+ fi
+}
+
+# defer takes a command, not a redirection.
+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope
+nsim_dev_del()
+{
+ echo "$1" > "$NSIM_DEV_SYS_DEL"
+}
+
+# Already reaped on the normal path, so ignore both failures.
+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope
+kill_wait()
+{
+ kill "$1" 2>/dev/null
+ wait "$1" 2>/dev/null
+ return 0
+}
+
+# Each resource is handed to defer only once it exists, so a run that loses a
+# race for one of the global names does not tear down the winner's.
+setup()
+{
+ set -e
+
+ echo "$NSIM_DEV_1_ID" > "$NSIM_DEV_SYS_NEW"
+ defer nsim_dev_del "$NSIM_DEV_1_ID"
+ echo "$NSIM_DEV_2_ID" > "$NSIM_DEV_SYS_NEW"
+ defer nsim_dev_del "$NSIM_DEV_2_ID"
+ udevadm settle 2>/dev/null || sleep 1
+
+ NSIM_DEV_1_NAME=$(find "$NSIM_DEV_1_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_1_SYS"/net -exec basename {} \;)
+ NSIM_DEV_2_NAME=$(find "$NSIM_DEV_2_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_2_SYS"/net -exec basename {} \;)
+
+ ip netns add nssv
+ defer ip netns del nssv
+ ip netns add nscl
+ defer ip netns del nscl
+
+ ip link set "$NSIM_DEV_1_NAME" netns nssv
+ ip link set "$NSIM_DEV_2_NAME" netns nscl
+
+ ip netns exec nssv ip addr add "$SRV_IP/24" dev "$NSIM_DEV_1_NAME"
+ ip netns exec nscl ip addr add "$CLI_IP/24" dev "$NSIM_DEV_2_NAME"
+
+ ip netns exec nssv ip link set dev "$NSIM_DEV_1_NAME" up
+ ip netns exec nscl ip link set dev "$NSIM_DEV_2_NAME" up
+
+ NSIM_DEV_1_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_1_FD}</var/run/netns/nssv
+ NSIM_DEV_1_IFIDX=$(ip netns exec nssv \
+ cat /sys/class/net/"$NSIM_DEV_1_NAME"/ifindex)
+
+ NSIM_DEV_2_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_2_FD}</var/run/netns/nscl
+ NSIM_DEV_2_IFIDX=$(ip netns exec nscl \
+ cat /sys/class/net/"$NSIM_DEV_2_NAME"/ifindex)
+
+ echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX" \
+ > "$NSIM_DEV_SYS_LINK"
+
+ SYNCDIR=$(mktemp -d)
+ defer rm -rf "$SYNCDIR"
+ set +e
+}
+
+feature()
+{
+ local netns="$1"
+ local dev="$2"
+ local feat="$3"
+
+ ip netns exec "$netns" ethtool -k "$dev" 2>/dev/null | \
+ sed -n "s/^$feat: \([a-z]*\).*/\1/p"
+}
+
+dbg_field()
+{
+ sed -n "s/.*\<$2=\([0-9]*\).*/\1/p" "$1" | head -1
+}
+
+tls_stat()
+{
+ ip netns exec "$1" cat /proc/net/tls_stat | \
+ awk -v name="$2" '$1 == name {print $2}'
+}
+
+# shellcheck disable=SC2317,SC2329 # invoked by name from slowwait
+both_ready()
+{
+ [ -e "$SYNCDIR/server.ready" ] && [ -e "$SYNCDIR/client.ready" ]
+}
+
+# Both ends park once their offload is installed and before any data is
+# sent, so the driver's context count can be sampled without racing the
+# transfer. Pass "nosample" when the offload is expected to be refused,
+# since then neither end ever reaches the barrier.
+run_pair()
+{
+ local sample="${1:-sample}"
+ local srv_rc cli_rc ready=1
+
+ rm -f "$SYNCDIR"/*.ready "$SYNCDIR"/go
+ CONNS_1=0
+ CONNS_2=0
+
+ defer_scope_push
+
+ ip netns exec nssv "$BIN" server "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local srv_pid=$!
+ defer kill_wait "$srv_pid"
+ ip netns exec nscl "$BIN" client "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local cli_pid=$!
+ defer kill_wait "$cli_pid"
+
+ if [ "$sample" = "sample" ]; then
+ # Comfortably longer than the helpers take to get to the
+ # barrier. Timing out here means a sample would be stale, so
+ # fail the run rather than assert on it.
+ slowwait "$READY_TIMEOUT_SEC" both_ready >/dev/null || ready=0
+ if [ "$ready" -eq 1 ]; then
+ CONNS_1=$(dbg_field "$NSIM_DEV_1_TLS" count)
+ CONNS_2=$(dbg_field "$NSIM_DEV_2_TLS" count)
+ : "${CONNS_1:=0}"
+ : "${CONNS_2:=0}"
+ fi
+ fi
+ touch "$SYNCDIR/go"
+
+ wait "$srv_pid"; srv_rc=$?
+ wait "$cli_pid"; cli_rc=$?
+
+ defer_scope_pop
+
+ [ "$ready" -eq 1 ] && [ "$srv_rc" -eq 0 ] && [ "$cli_rc" -eq 0 ]
+}
+
+###
+### Code start
+###
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: need root"
+ exit "$ksft_skip"
+fi
+
+if ! command -v ethtool >/dev/null; then
+ echo "SKIP: ethtool not found"
+ exit "$ksft_skip"
+fi
+
+if [ ! -x "$BIN" ]; then
+ echo "SKIP: $BIN not built"
+ exit "$ksft_skip"
+fi
+
+modprobe netdevsim 2>/dev/null
+if [ ! -d /sys/bus/netdevsim ]; then
+ echo "SKIP: netdevsim not available"
+ exit "$ksft_skip"
+fi
+
+modprobe tls 2>/dev/null
+if [ ! -e /proc/net/tls_stat ]; then
+ echo "SKIP: kernel TLS not available"
+ exit "$ksft_skip"
+fi
+
+trap defer_scopes_cleanup EXIT
+setup
+
+# /proc/net/tls_stat only proves generic kTLS. Without CONFIG_TLS_DEVICE the
+# netdevsim TLS hooks are stubbed out: no offload features and no per-port tls
+# debugfs file. That is an unsupported configuration for this test, not a
+# failure - skip it.
+if [ ! -e "$NSIM_DEV_1_TLS" ]; then
+ echo "SKIP: netdevsim built without TLS device offload (CONFIG_TLS_DEVICE=n)"
+ exit "$ksft_skip"
+fi
+
+# The offload has to be advertised, and on by default like the other
+# netdevsim crypto offloads.
+for f in tls-hw-tx-offload tls-hw-rx-offload; do
+ [ "$(feature nssv "$NSIM_DEV_1_NAME" "$f")" = "on" ]
+ check "$f advertised and on by default" $?
+done
+
+[ -e "$NSIM_DEV_1_TLS" ]
+check "per-port debugfs tls file exists" $?
+
+# Main data path run.
+run_pair
+check "offloaded TLS data transfer" $?
+
+# Sampled at the barrier, so each port must be holding exactly the TX and
+# the RX context of its own socket.
+[ "$CONNS_1" -eq 2 ] && [ "$CONNS_2" -eq 2 ]
+check "tx and rx contexts installed on both ports" $?
+
+# Both ends must have gone through the device path, not the SW fallback.
+[ "$(tls_stat nssv TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nssv TlsRxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsRxDevice)" -ge 1 ]
+check "both ends used the device path" $?
+
+[ "$(tls_stat nssv TlsTxSw)" -eq 0 ] && [ "$(tls_stat nscl TlsTxSw)" -eq 0 ]
+check "no silent fallback to the software path" $?
+
+[ "$(tls_stat nssv TlsDecryptError)" -eq 0 ] && \
+ [ "$(tls_stat nscl TlsDecryptError)" -eq 0 ]
+check "no decrypt errors" $?
+
+# The driver must have seen the records go by in both directions.
+[ "$(dbg_field "$NSIM_DEV_1_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_1_TLS" rx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" rx_packets)" -ge 1 ]
+check "driver counted offloaded packets both ways" $?
+
+# Sockets are closed by now, so every context must have been given back.
+# TX contexts are torn down from a workqueue after the socket is destroyed
+# (tls_device_sk_destruct() -> tls_device_tx_del_task), so tls_dev_del() may
+# not have run yet - poll rather than sample once.
+# shellcheck disable=SC2317,SC2329 # invoked by name from busywait
+all_ctx_released()
+{
+ [ "$(dbg_field "$NSIM_DEV_1_TLS" count)" -eq 0 ] &&
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" count)" -eq 0 ]
+}
+
+busywait 2000 all_ctx_released >/dev/null
+check "all offload contexts released on close" $?
+
+[ "$(tls_stat nssv TlsCurrTxDevice)" -eq 0 ] && \
+ [ "$(tls_stat nssv TlsCurrRxDevice)" -eq 0 ]
+check "no device contexts left behind" $?
+
+set_features()
+{
+ local want="$1"
+ local ret=0
+ local f
+
+ for f in tls-hw-tx-offload tls-hw-rx-offload; do
+ ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" "$f" "$want"
+ ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" "$f" "$want"
+
+ [ "$(feature nssv "$NSIM_DEV_1_NAME" "$f")" = "$want" ] || ret=1
+ [ "$(feature nscl "$NSIM_DEV_2_NAME" "$f")" = "$want" ] || ret=1
+ done
+
+ return "$ret"
+}
+
+# Turning the features off has to make the offload refuse the connection;
+# the test binary insists on the device path, so it must now fail.
+set_features off
+check "both offload features turned off on both ports" $?
+
+run_pair nosample
+rc=$?
+[ "$rc" -ne 0 ]
+check "offload declined once the feature is off" $?
+
+# Both directions on both ends have to have landed on the software path.
+[ "$(tls_stat nssv TlsTxSw)" -ge 1 ] && [ "$(tls_stat nssv TlsRxSw)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsTxSw)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsRxSw)" -ge 1 ]
+check "software path used when offload is off" $?
+
+set_features on
+check "both offload features turned back on" $?
+
+run_pair
+check "offload works again after re-enabling" $?
+
+echo
+echo "passed: $num_pass failed: $num_fail"
+[ "$num_fail" -eq 0 ] && exit 0
+exit 1
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
new file mode 100644
index 0000000000000..0d7c8950ca6ce
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kTLS device offload data path exercise, driven by tls.sh.
+ *
+ * One instance runs as the server and one as the client, each in its own
+ * network namespace, connected back to back by a linked netdevsim pair.
+ * Both ends enable kTLS and rely on netdevsim's emulated TLS offload, so
+ * every record travels through net/tls/tls_device.c rather than the
+ * software path.
+ *
+ * The two processes rendezvous through a shared directory so that neither
+ * side sends before the other has installed its RX offload.
+ */
+
+#define _GNU_SOURCE
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/tls.h>
+
+#ifndef SOL_TLS
+#define SOL_TLS 282
+#endif
+
+#ifndef TCP_ULP
+#define TCP_ULP 31
+#endif
+
+#define BULK_LEN (200 * 1024)
+#define MORE_FRAGS 64
+#define SPLICE_FRAG_LEN 4096
+#define SPLICE_FRAGS 8
+
+/* TLS_MIN_RECORD_SIZE_LIM and TLS_MAX_PAYLOAD_SIZE, which are not uapi. */
+#define REC_LIM_MIN 64
+#define REC_LIM_MAX 16384
+
+#define SMALL_RECS 100
+#define SMALL_LEN (REC_LIM_MIN * SMALL_RECS)
+
+#define SYNC_TIMEOUT_MS 20000
+#define CONNECT_TIMEOUT_MS 20000
+
+/* accept() and the transfers have no timeout of their own, and tls.sh waits
+ * on both helpers. Bound the whole run so a stuck peer cannot hang the test.
+ */
+#define RUN_TIMEOUT_SEC 120
+
+static const char *role;
+
+static void die(const char *what)
+{
+ fprintf(stderr, "%s: %s: %s\n", role, what, strerror(errno));
+ exit(1);
+}
+
+static void fail(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", role);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void msleep(unsigned int ms)
+{
+ struct timespec ts = {
+ .tv_sec = ms / 1000,
+ .tv_nsec = (ms % 1000) * 1000000L,
+ };
+
+ nanosleep(&ts, NULL);
+}
+
+/* /proc/net/tls_stat is per netns, so both ends can check that their own
+ * connection really landed on the device path.
+ */
+static unsigned long read_tls_stat(const char *name)
+{
+ char line[256];
+ unsigned long val;
+ FILE *f;
+
+ f = fopen("/proc/net/tls_stat", "r");
+ if (!f)
+ die("open /proc/net/tls_stat");
+
+ while (fgets(line, sizeof(line), f)) {
+ char key[64];
+
+ if (sscanf(line, "%63s %lu", key, &val) != 2)
+ continue;
+ if (!strcmp(key, name)) {
+ fclose(f);
+ return val;
+ }
+ }
+
+ fclose(f);
+ fail("%s not found in /proc/net/tls_stat", name);
+ return 0;
+}
+
+static void fill_pattern(char *buf, size_t len, unsigned int seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = (char)(seed + i * 31 + (i >> 8) * 7);
+}
+
+static void check_pattern(const char *buf, size_t len, unsigned int seed,
+ const char *what)
+{
+ char *want = malloc(len);
+ size_t i;
+
+ if (!want)
+ die("malloc");
+
+ fill_pattern(want, len, seed);
+ for (i = 0; i < len; i++) {
+ if (buf[i] != want[i])
+ fail("%s: payload mismatch at byte %zu: got 0x%02x want 0x%02x",
+ what, i, (unsigned char)buf[i],
+ (unsigned char)want[i]);
+ }
+
+ free(want);
+}
+
+static void write_all(int fd, const char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = send(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("send");
+ }
+ done += n;
+ }
+}
+
+static void read_all(int fd, char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = recv(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("recv");
+ }
+ if (n == 0)
+ fail("peer closed after %zu of %zu bytes", done, len);
+ done += n;
+ }
+}
+
+static void enable_ktls(int fd)
+{
+ struct tls12_crypto_info_aes_gcm_128 ci = {};
+ unsigned long tx_before, rx_before;
+
+ tx_before = read_tls_stat("TlsTxDevice");
+ rx_before = read_tls_stat("TlsRxDevice");
+
+ if (setsockopt(fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls")))
+ die("setsockopt(TCP_ULP, tls)");
+
+ ci.info.version = TLS_1_2_VERSION;
+ ci.info.cipher_type = TLS_CIPHER_AES_GCM_128;
+ memset(ci.iv, 'i', sizeof(ci.iv));
+ memset(ci.key, 'k', sizeof(ci.key));
+ memset(ci.salt, 's', sizeof(ci.salt));
+ memset(ci.rec_seq, 0, sizeof(ci.rec_seq));
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_TX)");
+ if (setsockopt(fd, SOL_TLS, TLS_RX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_RX)");
+
+ /* The whole point of the exercise: refuse to silently fall back to
+ * the software path, otherwise the test would pass without ever
+ * touching tls_device.c.
+ */
+ if (read_tls_stat("TlsTxDevice") != tx_before + 1)
+ fail("TX did not land on the device path (TlsTxDevice %lu -> %lu)",
+ tx_before, read_tls_stat("TlsTxDevice"));
+ if (read_tls_stat("TlsRxDevice") != rx_before + 1)
+ fail("RX did not land on the device path (TlsRxDevice %lu -> %lu)",
+ rx_before, read_tls_stat("TlsRxDevice"));
+}
+
+static void sync_path(char *out, size_t len, const char *dir, const char *who)
+{
+ if ((size_t)snprintf(out, len, "%s/%s.ready", dir, who) >= len)
+ fail("sync dir path too long");
+}
+
+static void rendezvous(const char *dir, const char *me, const char *peer)
+{
+ char mine[PATH_MAX], theirs[PATH_MAX];
+ unsigned int waited = 0;
+ int fd;
+
+ sync_path(mine, sizeof(mine), dir, me);
+ sync_path(theirs, sizeof(theirs), dir, peer);
+
+ fd = open(mine, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0)
+ die("create sync file");
+ close(fd);
+
+ while (access(theirs, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for %s", peer);
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Both ends stop here with their offload installed and no data sent yet,
+ * so that the driver state can be inspected from the outside.
+ */
+static void wait_for_go(const char *dir)
+{
+ unsigned int waited = 0;
+ char go[PATH_MAX];
+
+ if ((size_t)snprintf(go, sizeof(go), "%s/go", dir) >= sizeof(go))
+ fail("sync dir path too long");
+
+ while (access(go, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for go");
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Small writes with MSG_MORE accumulate into one open record before it is
+ * pushed, which is the interesting part of tls_push_data().
+ */
+static void send_msg_more(int fd, unsigned int seed)
+{
+ char buf[MORE_FRAGS + 1];
+ int i;
+
+ fill_pattern(buf, sizeof(buf), seed);
+
+ for (i = 0; i < MORE_FRAGS; i++) {
+ if (send(fd, buf + i, 1, MSG_MORE) != 1)
+ die("send(MSG_MORE)");
+ }
+ if (send(fd, buf + MORE_FRAGS, 1, 0) != 1)
+ die("send(last)");
+}
+
+/* splice() reaches tls_push_data() with MSG_SPLICE_PAGES once
+ * TLS_TX_ZEROCOPY_RO is enabled, which is a distinct fragment path.
+ */
+static void send_splice(int fd, unsigned int seed)
+{
+ char buf[SPLICE_FRAG_LEN];
+ int val = 1;
+ int i;
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO)");
+
+ for (i = 0; i < SPLICE_FRAGS; i++) {
+ int p[2];
+
+ fill_pattern(buf, sizeof(buf), seed + i * SPLICE_FRAG_LEN);
+
+ if (pipe(p))
+ die("pipe");
+ if (write(p[1], buf, sizeof(buf)) != sizeof(buf))
+ die("write to pipe");
+ if (splice(p[0], NULL, fd, NULL, sizeof(buf),
+ i == SPLICE_FRAGS - 1 ? 0 : SPLICE_F_MORE) !=
+ sizeof(buf))
+ die("splice");
+ close(p[0]);
+ close(p[1]);
+ }
+
+ val = 0;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO off)");
+}
+
+/* A record can be up to 16K, so normally one segment carries a piece of a
+ * single record. Shrinking the limit puts a dozen or so whole records in
+ * every segment instead, which is the multi-record path through the driver.
+ */
+static void send_small_records(int fd, unsigned int seed)
+{
+ char buf[SMALL_LEN];
+ uint16_t limit;
+
+ limit = REC_LIM_MIN;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN)");
+
+ fill_pattern(buf, sizeof(buf), seed);
+ write_all(fd, buf, sizeof(buf));
+
+ limit = REC_LIM_MAX;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN restore)");
+}
+
+#define SEED_C2S_BULK 0x11
+#define SEED_S2C_BULK 0x22
+#define SEED_C2S_MORE 0x33
+#define SEED_C2S_SPLICE 0x44
+#define SEED_C2S_SMALL 0x55
+
+static void run_client(int fd)
+{
+ char *buf = malloc(BULK_LEN);
+
+ if (!buf)
+ die("malloc");
+
+ fill_pattern(buf, BULK_LEN, SEED_C2S_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_S2C_BULK, "server -> client bulk");
+
+ send_msg_more(fd, SEED_C2S_MORE);
+ send_splice(fd, SEED_C2S_SPLICE);
+ send_small_records(fd, SEED_C2S_SMALL);
+
+ /* Wait for the server's verdict before tearing anything down. */
+ read_all(fd, buf, 1);
+ if (buf[0] != 'k')
+ fail("server reported a failure");
+
+ free(buf);
+}
+
+static void run_server(int fd)
+{
+ size_t splice_len = (size_t)SPLICE_FRAG_LEN * SPLICE_FRAGS;
+ char *buf = malloc(BULK_LEN);
+ char more[MORE_FRAGS + 1];
+ char *sbuf;
+ char ok = 'k';
+ int i;
+
+ sbuf = malloc(splice_len);
+ if (!buf || !sbuf)
+ die("malloc");
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_C2S_BULK, "client -> server bulk");
+
+ fill_pattern(buf, BULK_LEN, SEED_S2C_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, more, sizeof(more));
+ check_pattern(more, sizeof(more), SEED_C2S_MORE, "client -> server MSG_MORE");
+
+ read_all(fd, sbuf, splice_len);
+ for (i = 0; i < SPLICE_FRAGS; i++)
+ check_pattern(sbuf + (size_t)i * SPLICE_FRAG_LEN,
+ SPLICE_FRAG_LEN, SEED_C2S_SPLICE +
+ i * SPLICE_FRAG_LEN, "client -> server splice");
+
+ read_all(fd, buf, SMALL_LEN);
+ check_pattern(buf, SMALL_LEN, SEED_C2S_SMALL,
+ "client -> server small records");
+
+ write_all(fd, &ok, 1);
+
+ free(sbuf);
+ free(buf);
+}
+
+static int do_server(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ int lfd, fd, one = 1;
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (lfd < 0)
+ die("socket");
+ if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
+ die("SO_REUSEADDR");
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad bind address %s", ip);
+
+ if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)))
+ die("bind");
+ if (listen(lfd, 1))
+ die("listen");
+
+ fd = accept(lfd, NULL, NULL);
+ if (fd < 0)
+ die("accept");
+ close(lfd);
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "server", "client");
+ wait_for_go(syncdir);
+
+ run_server(fd);
+
+ close(fd);
+ return 0;
+}
+
+static int do_client(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ unsigned int waited = 0;
+ int fd;
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad server address %s", ip);
+
+ for (;;) {
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ die("socket");
+ if (!connect(fd, (struct sockaddr *)&sa, sizeof(sa)))
+ break;
+ close(fd);
+ if (waited >= CONNECT_TIMEOUT_MS)
+ die("connect");
+ msleep(20);
+ waited += 20;
+ }
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "client", "server");
+ wait_for_go(syncdir);
+
+ run_client(fd);
+
+ close(fd);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int port;
+
+ if (argc != 5) {
+ fprintf(stderr,
+ "usage: %s server|client <ip> <port> <syncdir>\n",
+ argv[0]);
+ return 2;
+ }
+
+ role = argv[1];
+ port = atoi(argv[3]);
+
+ alarm(RUN_TIMEOUT_SEC);
+
+ if (!strcmp(role, "server"))
+ return do_server(argv[2], port, argv[4]);
+ if (!strcmp(role, "client"))
+ return do_client(argv[2], port, argv[4]);
+
+ fprintf(stderr, "unknown role %s\n", role);
+ return 2;
+}
|
| 3/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19429 output: 18 thoughts: 494 Thoughts: **Analyzing the Netdevsim TLS Offload Patch** Okay, so the task is to dive into this patch series. First, let's break down what was actually changed in HEAD. I see the diff spans quite a few files. Looking at the changes, it seems we're enabling TLS device offload emulation *within* the `netdevsim` driver. Specifically, the following happened: 1. `drivers/net/Kconfig`: A `NETDEVSIM` option was added with `depends on TLS || TLS=n`. 2. `drivers/net/netdevsim/Makefile`: Added `tls.o` compilation conditionally on `CONFIG_TLS_DEVICE`. 3. `drivers/net/netdevsim/netdev.c`: Hooks for `nsim_do_tls`, `nsim_tls_init`, and `nsim_tls_teardown` are now present. 4. `drivers/net/netdevsim/netdevsim.h`: New struct `nsim_tls` and prototypes for the TLS functions. 5. `drivers/net/netdevsim/tls.c`: This is the meat of it: TLS device offload emulation for `netdevsim` with a new `nsim_tlsdev_ops` structure. The ops include `nsim_tls_dev_add`, `nsim_tls_dev_del`, `nsim_tls_dev_resync`. There's also some debugfs stuff and packet processing logic in here, like zeroing tags and dealing with rx marks. 6. Finally, self-tests for the netdevsim TLS offload functionality have been added. My initial thought is, "Is this something we should be fuzzing? And what are the key configurations and symbols to be aware of?" Let's see... the architecture of `netdevsim` is something I should look at because this is where the fuzzing potential lies. Right, `netdevsim` is a software simulator/mock driver. That's good. Very good. Because it's software, it's easily accessible in virtualized environments like QEMU or GCE. This makes it perfect for tools like syzkaller. Wait a second... *does* syzkaller already fuzz `netdevsim` and, more importantly, the TLS device offload functionality? Yes! Perfect! I've seen it tested heavily via devlink/sysfs or netlink. So this all looks *extremely* promising for fuzzing. I need to check the Kconfig options now to figure out exactly how to enable both `netdevsim` and the TLS device functionality. Specifically, I'll need to look in `net/tls/Kconfig` or `drivers/net/Kconfig` to see the dependencies that enable `TLS_DEVICE`, `NETDEVSIM` and `TLS`. |
| 4/2 | 2026/09/02 05:56 | tool | grepper |
0mArgs:{
"Expression": "config TLS_DEVICE"
}Results: {
"Output": "net/tls/Kconfig=5=config TLS\n--\nnet/tls/Kconfig-18-\nnet/tls/Kconfig:19:config TLS_DEVICE\nnet/tls/Kconfig-20-\tbool \"Transport Layer Security HW offload\"\n"
} |
| 5/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 20635 output: 32 thoughts: 21 Thoughts: Here's my summary, as though I'm reading and processing the `net/tls/Kconfig` file: **Diving into the TLS Configuration Options** Alright, let's get into this `net/tls/Kconfig` file. I'm going to start by scanning the first 30 lines or so. I expect this section to establish the basic framework for configuring TLS within the network stack. I'm looking for the top-level options, the dependencies, and the overall structure of how things are organized. I'm anticipating finding the main entry point for enabling TLS features, likely a `config` symbol. There might be some immediate dependencies on other core networking components that I'll need to note. Also, given the location of this file, I would expect a series of granular configurations that give us control over various aspects of TLS: cipher suites, supported versions, perhaps hardware acceleration support if applicable, and default behaviors. Let's see if those expectations are met. I'll mentally map out the hierarchy as I go; the goal is to quickly build a mental model of the available options and their relationships. Let's get to it! |
| 6/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "net/tls/Kconfig",
"FirstLine": 1,
"LineCount": 35
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t#\n 3:\t# TLS configuration\n 4:\t#\n 5:\tconfig TLS\n 6:\t\ttristate \"Transport Layer Security support\"\n 7:\t\tdepends on INET\n 8:\t\tselect CRYPTO\n 9:\t\tselect CRYPTO_AES\n 10:\t\tselect CRYPTO_GCM\n 11:\t\tselect NET_SOCK_MSG\n 12:\t\tdefault n\n 13:\t\thelp\n 14:\t\tEnable kernel support for TLS protocol. This allows symmetric\n 15:\t\tencryption handling of the TLS protocol to be done in-kernel.\n 16:\t\n 17:\t\tIf unsure, say N.\n 18:\t\n 19:\tconfig TLS_DEVICE\n 20:\t\tbool \"Transport Layer Security HW offload\"\n 21:\t\tdepends on TLS\n 22:\t\tselect SKB_DECRYPTED\n 23:\t\tselect SOCK_VALIDATE_XMIT\n 24:\t\tselect SOCK_RX_QUEUE_MAPPING\n 25:\t\tdefault n\n 26:\t\thelp\n 27:\t\tEnable kernel support for HW offload of the TLS protocol.\n 28:\t\n 29:\t\tIf unsure, say N.\n"
} |
| 7/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21256 output: 34 thoughts: 18 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the NETDEVSIM Kconfig** Alright, time to get this sorted. My immediate thought is to head straight for `drivers/net/Kconfig`. That's where all the fundamental configuration options for network device drivers reside, and if NETDEVSIM is anything like the other simulated devices, its configuration controls will definitely be defined there. I need to understand the dependencies, if it's enabled by default, which other features it might conflict with, and ultimately, how it's integrated into the bigger picture of the network stack configuration. Time to dig in and see what's what. This is where the real work begins. |
| 8/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/Kconfig",
"FirstLine": 605,
"LineCount": 25
}Results: {
"Contents": " 605:\t\ttristate \"Simulated networking device\"\n 606:\t\tdepends on DEBUG_FS\n 607:\t\tdepends on INET\n 608:\t\tdepends on PSAMPLE || PSAMPLE=n\n 609:\t\tdepends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n\n 610:\t\tdepends on TLS || TLS=n\n 611:\t\tselect NET_DEVLINK\n 612:\t\tselect PAGE_POOL\n 613:\t\tselect NET_SHAPER\n 614:\t\thelp\n 615:\t\t This driver is a developer testing tool and software model that can\n 616:\t\t be used to test various control path networking APIs, especially\n 617:\t\t HW-offload related.\n 618:\t\n 619:\t\t To compile this driver as a module, choose M here: the module\n 620:\t\t will be called netdevsim.\n 621:\t\n 622:\tconfig NET_FAILOVER\n 623:\t\ttristate \"Failover driver\"\n 624:\t\tselect FAILOVER\n 625:\t\thelp\n 626:\t\t This provides an automated failover mechanism via APIs to create\n 627:\t\t and destroy a failover master netdev and manages a primary and\n 628:\t\t standby slave netdevs that get registered via the generic failover\n 629:\t\t infrastructure. This can be used by paravirtual drivers to enable\n"
} |
| 9/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21854 output: 38 thoughts: 49 Thoughts: **Examining the TLS Implementation in Netdevsim** Okay, let's dive into the guts of the TLS implementation for Netdevsim. I need a quick overview, so I'll start by checking out `drivers/net/netdevsim/tls.c`. My primary interest is in understanding the public interface and any callbacks, so I'm specifically looking for the functions that are either exported, non-static, or registered as callbacks. This should give me a good handle on how the TLS functionality is exposed and integrated within the Netdevsim framework. Let's see what we've got in that file. Time to read through `drivers/net/netdevsim/tls.c` and get a feel for the design. |
| 10/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/tls.c",
"FirstLine": 1,
"LineCount": 100
}Results: {
"Contents": " 1:\t// SPDX-License-Identifier: GPL-2.0\n 2:\t\n 3:\t#include \u003clinux/ipv6.h\u003e\n 4:\t#include \u003clinux/list.h\u003e\n 5:\t#include \u003clinux/skbuff.h\u003e\n 6:\t#include \u003clinux/slab.h\u003e\n 7:\t#include \u003clinux/tcp.h\u003e\n 8:\t#include \u003cnet/ip.h\u003e\n 9:\t#include \u003cnet/ipv6.h\u003e\n 10:\t#include \u003cnet/tcp.h\u003e\n 11:\t#include \u003cnet/tls.h\u003e\n 12:\t\n 13:\t#include \"netdevsim.h\"\n 14:\t\n 15:\t/* Emulated kTLS offload. No crypto is performed: TX puts the record on the\n 16:\t * wire as plaintext and RX just marks it decrypted, so both ports have to\n 17:\t * have the offload installed on the connection before any data flows. That\n 18:\t * is the only supported setup. Anything else - a peer with no matching\n 19:\t * context, a context installed mid stream - has the core run the software\n 20:\t * AEAD over plaintext, which fails the connection with -EBADMSG.\n 21:\t */\n 22:\t\n 23:\t#define NSIM_TLS_MAX_CONN\t32\n 24:\t\n 25:\t/* netdev_fix_features() drops NETIF_F_HW_TLS_RX unless the device also does\n 26:\t * RX checksums, which every offload capable NIC does.\n 27:\t */\n 28:\t#define NSIM_TLS_FEATURES\t(NETIF_F_HW_TLS_TX | NETIF_F_HW_TLS_RX | \\\n 29:\t\t\t\t\t NETIF_F_RXCSUM)\n 30:\t\n 31:\tstruct nsim_tls_conn {\n 32:\t\tstruct list_head list;\n 33:\t\tstruct rcu_head rcu;\n 34:\t\n 35:\t\t/* Identity as seen on the wire, from the point of view of the port\n 36:\t\t * the offload was installed on: l* is this side, r* is the peer.\n 37:\t\t */\n 38:\t\tstruct in6_addr laddr;\n 39:\t\tstruct in6_addr raddr;\n 40:\t\t__be16 lport;\n 41:\t\t__be16 rport;\n 42:\t\tu16 family;\n 43:\t\n 44:\t\tenum tls_offload_ctx_dir dir;\n 45:\t\tu32 start_sn;\n 46:\t\tbool started;\n 47:\t\tu16 cipher_type;\n 48:\t\tconst struct tls_context *tls_ctx;\n 49:\t};\n 50:\t\n 51:\tstruct nsim_tls_tuple {\n 52:\t\tstruct in6_addr saddr;\n 53:\t\tstruct in6_addr daddr;\n 54:\t\t__be16 sport;\n 55:\t\t__be16 dport;\n 56:\t\tu32 seq;\n 57:\t};\n 58:\t\n 59:\t/* Parse the tuple from the frame the way a NIC would. The xmit skb is not\n 60:\t * always a well formed local TCP skb (AF_PACKET, tc redirect); use\n 61:\t * skb_header_pointer() so a non-linear or truncated header cannot be misread.\n 62:\t */\n 63:\tstatic bool nsim_tls_parse(const struct sk_buff *skb,\n 64:\t\t\t\t struct nsim_tls_tuple *t)\n 65:\t{\n 66:\t\tint off = skb_network_offset(skb);\n 67:\t\tconst struct tcphdr *th;\n 68:\t\tstruct tcphdr _th;\n 69:\t\n 70:\t\tswitch (skb-\u003eprotocol) {\n 71:\t\tcase htons(ETH_P_IP): {\n 72:\t\t\tconst struct iphdr *iph;\n 73:\t\t\tstruct iphdr _iph;\n 74:\t\n 75:\t\t\tiph = skb_header_pointer(skb, off, sizeof(_iph), \u0026_iph);\n 76:\t\t\tif (!iph || iph-\u003eversion != 4 || iph-\u003eihl \u003c 5 ||\n 77:\t\t\t iph-\u003eprotocol != IPPROTO_TCP || ip_is_fragment(iph))\n 78:\t\t\t\treturn false;\n 79:\t\t\tipv6_addr_set_v4mapped(iph-\u003esaddr, \u0026t-\u003esaddr);\n 80:\t\t\tipv6_addr_set_v4mapped(iph-\u003edaddr, \u0026t-\u003edaddr);\n 81:\t\t\toff += iph-\u003eihl * 4;\n 82:\t\t\tbreak;\n 83:\t\t}\n 84:\t\tcase htons(ETH_P_IPV6): {\n 85:\t\t\tconst struct ipv6hdr *ip6h;\n 86:\t\t\tstruct ipv6hdr _ip6h;\n 87:\t\t\t__be16 frag_off;\n 88:\t\t\tu8 nexthdr;\n 89:\t\t\tint thoff;\n 90:\t\n 91:\t\t\tip6h = skb_header_pointer(skb, off, sizeof(_ip6h), \u0026_ip6h);\n 92:\t\t\tif (!ip6h)\n 93:\t\t\t\treturn false;\n 94:\t\n 95:\t\t\tnexthdr = ip6h-\u003enexthdr;\n 96:\t\t\tthoff = ipv6_skip_exthdr(skb, off + sizeof(_ip6h), \u0026nexthdr,\n 97:\t\t\t\t\t\t \u0026frag_off);\n 98:\t\t\tif (thoff \u003c 0 || nexthdr != IPPROTO_TCP || frag_off)\n 99:\t\t\t\treturn false;\n 100:\t\n"
} |
| 11/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23596 output: 40 thoughts: 20 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into `drivers/net/netdevsim/tls.c`** Okay, time to dive into `drivers/net/netdevsim/tls.c`. I need to get a better grasp of how the TLS functionality is implemented within the netdevsim framework. I'm already pretty familiar with the general architecture – how netdevsim provides a simulated network environment for testing and development. My main focus here is understanding the specifics of the TLS integration, specifically: How it's hooked into the simulated data path, how key management (if any) is handled, and what level of TLS support they've implemented. Are we talking about a full TLS stack, or just emulating the handshake and some basic encryption? Understanding the implementation choices will allow me to assess the fidelity of the simulation and identify any potential gotchas for my use case. I'm particularly interested in looking for any performance implications of running TLS within a simulated environment. Are there any bottlenecks introduced by the simulation overhead? Also, I need to see if any custom extensions or modifications to standard TLS libraries are present here. That's what I'll be looking for! Let's get reading! |
| 12/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/tls.c",
"FirstLine": 101,
"LineCount": 100
}Results: {
"Contents": " 101:\t\t\tt-\u003esaddr = ip6h-\u003esaddr;\n 102:\t\t\tt-\u003edaddr = ip6h-\u003edaddr;\n 103:\t\t\toff = thoff;\n 104:\t\t\tbreak;\n 105:\t\t}\n 106:\t\tdefault:\n 107:\t\t\treturn false;\n 108:\t\t}\n 109:\t\n 110:\t\tth = skb_header_pointer(skb, off, sizeof(_th), \u0026_th);\n 111:\t\tif (!th)\n 112:\t\t\treturn false;\n 113:\t\tt-\u003esport = th-\u003esource;\n 114:\t\tt-\u003edport = th-\u003edest;\n 115:\t\tt-\u003eseq = ntohl(th-\u003eseq);\n 116:\t\n 117:\t\treturn true;\n 118:\t}\n 119:\t\n 120:\t/* No socket on RX yet, so match the tuple installed at -\u003etls_dev_add(), the\n 121:\t * way the hardware does.\n 122:\t */\n 123:\tstatic bool nsim_tls_rx_offloaded(struct netdevsim *ns,\n 124:\t\t\t\t\t const struct nsim_tls_tuple *t)\n 125:\t{\n 126:\t\tstruct nsim_tls_conn *conn;\n 127:\t\n 128:\t\tlist_for_each_entry_rcu(conn, \u0026ns-\u003etls.conns, list) {\n 129:\t\t\tif (conn-\u003edir != TLS_OFFLOAD_CTX_DIR_RX ||\n 130:\t\t\t conn-\u003elport != t-\u003edport || conn-\u003erport != t-\u003esport ||\n 131:\t\t\t !ipv6_addr_equal(\u0026conn-\u003eladdr, \u0026t-\u003edaddr) ||\n 132:\t\t\t !ipv6_addr_equal(\u0026conn-\u003eraddr, \u0026t-\u003esaddr))\n 133:\t\t\t\tcontinue;\n 134:\t\n 135:\t\t\t/* Anything before start_sn predates the offload and has to\n 136:\t\t\t * stay encrypted. Only worth asking once: TCP sequence\n 137:\t\t\t * numbers wrap, and a stream that has moved 2G past a\n 138:\t\t\t * sequence it is still compared against looks like it went\n 139:\t\t\t * backwards.\n 140:\t\t\t */\n 141:\t\t\tif (!READ_ONCE(conn-\u003estarted)) {\n 142:\t\t\t\tif (before(t-\u003eseq, conn-\u003estart_sn))\n 143:\t\t\t\t\treturn false;\n 144:\t\t\t\tWRITE_ONCE(conn-\u003estarted, true);\n 145:\t\t\t}\n 146:\t\n 147:\t\t\treturn true;\n 148:\t\t}\n 149:\t\n 150:\t\treturn false;\n 151:\t}\n 152:\t\n 153:\t/* The stack leaves an uninitialized, tag sized gap at the tail of each record\n 154:\t * for the device to write the auth tag into. We do not encrypt, so zero it\n 155:\t * to avoid leaking that memory onto the wire. Nothing tells the device where\n 156:\t * the tag is, so walk the records with tls_get_record() and clear the end of\n 157:\t * each one's last frag.\n 158:\t */\n 159:\tstatic void nsim_tls_tx_zero_tags(struct sk_buff *skb)\n 160:\t{\n 161:\t\tstruct tls_context *ctx = tls_get_ctx(skb-\u003esk);\n 162:\t\tconst struct tcphdr *th = tcp_hdr(skb);\n 163:\t\tstruct tls_offload_context_tx *tx_ctx;\n 164:\t\tu32 seq, end, tag_size;\n 165:\t\tunsigned long flags;\n 166:\t\tunsigned int off;\n 167:\t\n 168:\t\toff = skb_transport_offset(skb) + __tcp_hdrlen(th);\n 169:\t\tif (off \u003e= skb-\u003elen)\n 170:\t\t\treturn;\n 171:\t\n 172:\t\ttag_size = ctx-\u003eprot_info.tag_size;\n 173:\t\tseq = ntohl(th-\u003eseq);\n 174:\t\tend = seq + skb-\u003elen - off;\n 175:\t\ttx_ctx = tls_offload_ctx_tx(ctx);\n 176:\t\n 177:\t\tspin_lock_irqsave(\u0026tx_ctx-\u003elock, flags);\n 178:\t\twhile (before(seq, end)) {\n 179:\t\t\tstruct tls_record_info *record;\n 180:\t\t\tskb_frag_t *frag;\n 181:\t\t\tu64 rcd_sn;\n 182:\t\n 183:\t\t\trecord = tls_get_record(tx_ctx, seq, \u0026rcd_sn);\n 184:\t\t\tif (!record || tls_record_is_start_marker(record))\n 185:\t\t\t\tbreak;\n 186:\t\n 187:\t\t\tfrag = \u0026record-\u003efrags[record-\u003enum_frags - 1];\n 188:\t\t\tmemset(skb_frag_address(frag) + skb_frag_size(frag) - tag_size,\n 189:\t\t\t 0, tag_size);\n 190:\t\n 191:\t\t\tseq = record-\u003eend_seq;\n 192:\t\t}\n 193:\t\tspin_unlock_irqrestore(\u0026tx_ctx-\u003elock, flags);\n 194:\t}\n 195:\t\n 196:\t/* Stand in for the inline encryption the hardware would do on the way out.\n 197:\t * The stack has already framed the record and we do not encrypt, so the tag\n 198:\t * is all that is left to deal with. The socket is right here, so identify\n 199:\t * the connection the way the real drivers do.\n 200:\t */\n"
} |
| 13/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 25485 output: 40 thoughts: 20 Thoughts: Here's a summary of my thought process, as if I were reviewing `drivers/net/netdevsim/tls.c`: **Diving into the TLS Implementation in Netdevsim** Okay, time to dig into the TLS implementation within the Netdevsim framework. I've already got a good grasp of the overall Netdevsim architecture, so now I want to understand how TLS is specifically handled. This file, `tls.c`, is the obvious starting point. I need to see how they've integrated TLS functionality into this simulated networking environment. I'm especially interested in how they're handling key generation, certificate management (if any), and the actual encryption/decryption process within the context of the simulated devices. Are they using OpenSSL, or is it a custom solution? What are the performance implications? Are they focusing on TLS for transport security or are they integrating it more deeply into the simulated network stack? Understanding these implementation details will be key to evaluating the accuracy and practicality of this simulation for testing TLS-related network configurations and behaviors. I'll be looking for potential bottlenecks, areas for optimization, and, of course, any potential security vulnerabilities, even in a simulated environment. Let's see what's in store. |
| 14/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/tls.c",
"FirstLine": 201,
"LineCount": 100
}Results: {
"Contents": " 201:\tstatic bool nsim_tls_tx(struct netdevsim *ns, struct sk_buff *skb)\n 202:\t{\n 203:\t\tif (!tls_is_skb_tx_device_offloaded(skb))\n 204:\t\t\treturn false;\n 205:\t\n 206:\t\t/* The question tls_validate_xmit_skb() asks: on anything other than\n 207:\t\t * the device the context is attached to, the core has already\n 208:\t\t * encrypted the record in software, so it is real ciphertext and none\n 209:\t\t * of our business.\n 210:\t\t */\n 211:\t\tif (rcu_dereference_bh(tls_get_ctx(skb-\u003esk)-\u003enetdev) != ns-\u003enetdev)\n 212:\t\t\treturn false;\n 213:\t\n 214:\t\tnsim_tls_tx_zero_tags(skb);\n 215:\t\tatomic64_inc(\u0026ns-\u003etls.tx_packets);\n 216:\t\tatomic64_add(skb-\u003elen, \u0026ns-\u003etls.tx_bytes);\n 217:\t\n 218:\t\treturn true;\n 219:\t}\n 220:\t\n 221:\t/* Stand in for the inline decryption the peer's hardware would do, since we\n 222:\t * are about to hand the skb to its stack. We do not decrypt either, so all\n 223:\t * that is needed is the flag that tells the kTLS core the payload is\n 224:\t * already plaintext.\n 225:\t */\n 226:\tstatic void nsim_tls_rx(struct netdevsim *ns, struct sk_buff *skb)\n 227:\t{\n 228:\t\tstruct nsim_tls_tuple t;\n 229:\t\n 230:\t\t/* Not every port registers the offload, and one that does not never\n 231:\t\t * initializes the list head, so gate on the count. It is the\n 232:\t\t * cheaper test anyway.\n 233:\t\t */\n 234:\t\tif (!READ_ONCE(ns-\u003etls.count))\n 235:\t\t\treturn;\n 236:\t\n 237:\t\tif (!nsim_tls_parse(skb, \u0026t))\n 238:\t\t\treturn;\n 239:\t\n 240:\t\tif (!nsim_tls_rx_offloaded(ns, \u0026t))\n 241:\t\t\treturn;\n 242:\t\n 243:\t\tskb-\u003edecrypted = 1;\n 244:\t\tatomic64_inc(\u0026ns-\u003etls.rx_packets);\n 245:\t\tatomic64_add(skb-\u003elen, \u0026ns-\u003etls.rx_bytes);\n 246:\t}\n 247:\t\n 248:\t/* netdevsim has no wire: nsim_start_xmit() hands the skb straight to the\n 249:\t * peer's receive path, so this one call site stands in for the hardware of\n 250:\t * both ports. It has to run before nsim_forward_skb() moves the skb over,\n 251:\t * which resets the headers.\n 252:\t */\n 253:\tvoid nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,\n 254:\t\t\t struct netdevsim *peer_ns)\n 255:\t{\n 256:\t\t/* nsim_do_psp() ran first and may have wrapped the record stream, so\n 257:\t\t * this is no longer a plain TLS over TCP packet. No NIC chains the\n 258:\t\t * two inline offloads either, so leave it alone.\n 259:\t\t */\n 260:\t\tif (skb-\u003eencapsulation)\n 261:\t\t\treturn;\n 262:\t\n 263:\t\t/* Only what this port just \"encrypted\" may skip decryption on the\n 264:\t\t * peer. Everything else - a software kTLS sender because the TX\n 265:\t\t * feature is off, a frame injected on the tuple - really is\n 266:\t\t * ciphertext or not TLS at all, and the core has to deal with it.\n 267:\t\t */\n 268:\t\tif (!nsim_tls_tx(ns, skb))\n 269:\t\t\treturn;\n 270:\t\n 271:\t\tnsim_tls_rx(peer_ns, skb);\n 272:\t}\n 273:\t\n 274:\tstatic int nsim_tls_dev_add(struct net_device *netdev, struct sock *sk,\n 275:\t\t\t\t enum tls_offload_ctx_dir direction,\n 276:\t\t\t\t struct tls_crypto_info *crypto_info,\n 277:\t\t\t\t u32 start_offload_tcp_sn)\n 278:\t{\n 279:\t\tstruct netdevsim *ns = netdev_priv(netdev);\n 280:\t\tstruct nsim_tls_conn *conn;\n 281:\t\tint ret = 0;\n 282:\t\n 283:\t\t/* Mirror the cipher support of a typical offload capable NIC. */\n 284:\t\tswitch (crypto_info-\u003ecipher_type) {\n 285:\t\tcase TLS_CIPHER_AES_GCM_128:\n 286:\t\tcase TLS_CIPHER_AES_GCM_256:\n 287:\t\t\tbreak;\n 288:\t\tdefault:\n 289:\t\t\treturn -EOPNOTSUPP;\n 290:\t\t}\n 291:\t\n 292:\t\tconn = kzalloc_obj(*conn);\n 293:\t\tif (!conn)\n 294:\t\t\treturn -ENOMEM;\n 295:\t\n 296:\t\tif (sk-\u003esk_family == AF_INET6) {\n 297:\t\t\tconn-\u003eladdr = sk-\u003esk_v6_rcv_saddr;\n 298:\t\t\tconn-\u003eraddr = sk-\u003esk_v6_daddr;\n 299:\t\t} else {\n 300:\t\t\tipv6_addr_set_v4mapped(sk-\u003esk_rcv_saddr, \u0026conn-\u003eladdr);\n"
} |
| 15/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 27440 output: 40 thoughts: 20 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into `drivers/net/netdevsim/tls.c`** Okay, time to dive into `drivers/net/netdevsim/tls.c`. I need to get a better grasp of how the TLS functionality is implemented within the netdevsim framework. I'm already pretty familiar with the general architecture – how netdevsim provides a simulated network environment for testing and development. My main focus here is understanding the specifics of the TLS integration, specifically: How it's hooked into the simulated data path, how key management (if any) is handled, and what level of TLS support they've implemented. Are we talking about a full TLS stack, or just emulating the handshake and some basic encryption? Understanding the implementation choices will allow me to assess the fidelity of the simulation and identify any potential gotchas for my use case. I'm particularly interested in looking for any performance implications of running TLS within a simulated environment. Are there any bottlenecks introduced by the simulation overhead? Also, I need to see if any custom extensions or modifications to standard TLS libraries are present here. That's what I'll be looking for! Let's get reading! |
| 16/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/tls.c",
"FirstLine": 301,
"LineCount": 100
}Results: {
"Contents": " 301:\t\t\tipv6_addr_set_v4mapped(sk-\u003esk_daddr, \u0026conn-\u003eraddr);\n 302:\t\t}\n 303:\t\tconn-\u003efamily = sk-\u003esk_family;\n 304:\t\tconn-\u003elport = htons(inet_sk(sk)-\u003einet_num);\n 305:\t\tconn-\u003erport = sk-\u003esk_dport;\n 306:\t\tconn-\u003edir = direction;\n 307:\t\tconn-\u003estart_sn = start_offload_tcp_sn;\n 308:\t\tconn-\u003ecipher_type = crypto_info-\u003ecipher_type;\n 309:\t\tconn-\u003etls_ctx = tls_get_ctx(sk);\n 310:\t\n 311:\t\tspin_lock_bh(\u0026ns-\u003etls.lock);\n 312:\t\tif (ns-\u003etls.count \u003e= NSIM_TLS_MAX_CONN) {\n 313:\t\t\tret = -ENOSPC;\n 314:\t\t\tgoto out_unlock;\n 315:\t\t}\n 316:\t\tlist_add_tail_rcu(\u0026conn-\u003elist, \u0026ns-\u003etls.conns);\n 317:\t\tns-\u003etls.count++;\n 318:\t\tif (direction == TLS_OFFLOAD_CTX_DIR_TX)\n 319:\t\t\tns-\u003etls.tx_conn++;\n 320:\t\telse\n 321:\t\t\tns-\u003etls.rx_conn++;\n 322:\tout_unlock:\n 323:\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n 324:\t\n 325:\t\tif (ret)\n 326:\t\t\tkfree(conn);\n 327:\t\n 328:\t\treturn ret;\n 329:\t}\n 330:\t\n 331:\tstatic void nsim_tls_dev_del(struct net_device *netdev,\n 332:\t\t\t\t struct tls_context *tls_ctx,\n 333:\t\t\t\t enum tls_offload_ctx_dir direction)\n 334:\t{\n 335:\t\tstruct netdevsim *ns = netdev_priv(netdev);\n 336:\t\tstruct nsim_tls_conn *conn;\n 337:\t\n 338:\t\tspin_lock_bh(\u0026ns-\u003etls.lock);\n 339:\t\tlist_for_each_entry(conn, \u0026ns-\u003etls.conns, list) {\n 340:\t\t\tif (conn-\u003etls_ctx != tls_ctx || conn-\u003edir != direction)\n 341:\t\t\t\tcontinue;\n 342:\t\n 343:\t\t\tlist_del_rcu(\u0026conn-\u003elist);\n 344:\t\t\tns-\u003etls.count--;\n 345:\t\t\tif (direction == TLS_OFFLOAD_CTX_DIR_TX)\n 346:\t\t\t\tns-\u003etls.tx_conn--;\n 347:\t\t\telse\n 348:\t\t\t\tns-\u003etls.rx_conn--;\n 349:\t\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n 350:\t\n 351:\t\t\tkfree_rcu(conn, rcu);\n 352:\t\t\treturn;\n 353:\t\t}\n 354:\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n 355:\t\n 356:\t\tnetdev_err(netdev, \"TLS %s context not found on del\\n\",\n 357:\t\t\t direction == TLS_OFFLOAD_CTX_DIR_TX ? \"tx\" : \"rx\");\n 358:\t}\n 359:\t\n 360:\t/* Nothing is ever out of sync here: the emulation does not decrypt, so it\n 361:\t * never loses the record boundaries and never asks for a resync. The core\n 362:\t * can still call in on the RX path, so account for it and move on.\n 363:\t */\n 364:\tstatic int nsim_tls_dev_resync(struct net_device *netdev, struct sock *sk,\n 365:\t\t\t\t u32 seq, u8 *rcd_sn,\n 366:\t\t\t\t enum tls_offload_ctx_dir direction)\n 367:\t{\n 368:\t\tstruct netdevsim *ns = netdev_priv(netdev);\n 369:\t\n 370:\t\tatomic64_inc(\u0026ns-\u003etls.resyncs);\n 371:\t\n 372:\t\treturn 0;\n 373:\t}\n 374:\t\n 375:\tstatic const struct tlsdev_ops nsim_tlsdev_ops = {\n 376:\t\t.tls_dev_add\t= nsim_tls_dev_add,\n 377:\t\t.tls_dev_del\t= nsim_tls_dev_del,\n 378:\t\t.tls_dev_resync\t= nsim_tls_dev_resync,\n 379:\t};\n 380:\t\n 381:\tstatic ssize_t nsim_tls_dbg_read(struct file *filp, char __user *buffer,\n 382:\t\t\t\t\t size_t count, loff_t *ppos)\n 383:\t{\n 384:\t\tstruct netdevsim *ns = filp-\u003eprivate_data;\n 385:\t\tstruct nsim_tls_conn *conn;\n 386:\t\tsize_t bufsize;\n 387:\t\tchar *buf, *p;\n 388:\t\tint len;\n 389:\t\n 390:\t\t/* Two full IPv6 addresses and ports fit in 160 bytes a line. */\n 391:\t\tbufsize = (NSIM_TLS_MAX_CONN * 160) + 200;\n 392:\t\tbuf = kzalloc(bufsize, GFP_KERNEL);\n 393:\t\tif (!buf)\n 394:\t\t\treturn -ENOMEM;\n 395:\t\n 396:\t\tp = buf;\n 397:\t\n 398:\t\tspin_lock_bh(\u0026ns-\u003etls.lock);\n 399:\t\tp += scnprintf(p, bufsize - (p - buf),\n 400:\t\t\t \"conn count=%u tx=%u rx=%u\\n\",\n"
} |
| 17/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 29366 output: 39 thoughts: 25 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing the Remainder of `drivers/net/netdevsim/tls.c`** Okay, I've got a handle on the initial part of this `tls.c` file within `netdevsim`. Now, let's dive into the rest. My immediate goal is to establish a comprehensive understanding of the code's functionality, especially in relation to TLS integration within this simulated network device. I need to see how the remaining sections contribute to the overall picture. I'm focusing on identifying any key data structures, core functions, and the control flow involved in establishing, managing, and tearing down TLS connections. Are there any unique aspects of the simulation environment that influence the TLS implementation, such as specific hooks or event handling? I'm going to pay close attention to error handling, resource management, and any interactions with the underlying network simulator to spot potential bottlenecks or areas that might require optimization, or where the simulation diverges from a "real world" implementation. I'll be looking for nuances; how this is implemented for this specific architecture matters. This will all give me the context I need to evaluate its effectiveness and identify any potential improvements or vulnerabilities in a simulated TLS environment. |
| 18/2 | 2026/09/02 05:56 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/tls.c",
"FirstLine": 401,
"LineCount": 85
}Results: {
"Contents": " 401:\t\t\t ns-\u003etls.count, ns-\u003etls.tx_conn, ns-\u003etls.rx_conn);\n 402:\t\tp += scnprintf(p, bufsize - (p - buf),\n 403:\t\t\t \"tx_packets=%llu tx_bytes=%llu rx_packets=%llu rx_bytes=%llu resyncs=%llu\\n\",\n 404:\t\t\t atomic64_read(\u0026ns-\u003etls.tx_packets),\n 405:\t\t\t atomic64_read(\u0026ns-\u003etls.tx_bytes),\n 406:\t\t\t atomic64_read(\u0026ns-\u003etls.rx_packets),\n 407:\t\t\t atomic64_read(\u0026ns-\u003etls.rx_bytes),\n 408:\t\t\t atomic64_read(\u0026ns-\u003etls.resyncs));\n 409:\t\n 410:\t\tlist_for_each_entry(conn, \u0026ns-\u003etls.conns, list) {\n 411:\t\t\tif (conn-\u003efamily == AF_INET6)\n 412:\t\t\t\tp += scnprintf(p, bufsize - (p - buf),\n 413:\t\t\t\t\t \"%s [%pI6c]:%u -\u003e [%pI6c]:%u cipher=%u sn=%u\\n\",\n 414:\t\t\t\t\t conn-\u003edir == TLS_OFFLOAD_CTX_DIR_TX ?\n 415:\t\t\t\t\t \"tx\" : \"rx\",\n 416:\t\t\t\t\t \u0026conn-\u003eladdr, ntohs(conn-\u003elport),\n 417:\t\t\t\t\t \u0026conn-\u003eraddr, ntohs(conn-\u003erport),\n 418:\t\t\t\t\t conn-\u003ecipher_type, conn-\u003estart_sn);\n 419:\t\t\telse\n 420:\t\t\t\tp += scnprintf(p, bufsize - (p - buf),\n 421:\t\t\t\t\t \"%s %pI4:%u -\u003e %pI4:%u cipher=%u sn=%u\\n\",\n 422:\t\t\t\t\t conn-\u003edir == TLS_OFFLOAD_CTX_DIR_TX ?\n 423:\t\t\t\t\t \"tx\" : \"rx\",\n 424:\t\t\t\t\t \u0026conn-\u003eladdr.s6_addr32[3],\n 425:\t\t\t\t\t ntohs(conn-\u003elport),\n 426:\t\t\t\t\t \u0026conn-\u003eraddr.s6_addr32[3],\n 427:\t\t\t\t\t ntohs(conn-\u003erport),\n 428:\t\t\t\t\t conn-\u003ecipher_type, conn-\u003estart_sn);\n 429:\t\t}\n 430:\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n 431:\t\n 432:\t\tlen = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);\n 433:\t\n 434:\t\tkfree(buf);\n 435:\t\n 436:\t\treturn len;\n 437:\t}\n 438:\t\n 439:\tstatic const struct file_operations nsim_tls_dbg_fops = {\n 440:\t\t.owner = THIS_MODULE,\n 441:\t\t.open = simple_open,\n 442:\t\t.read = nsim_tls_dbg_read,\n 443:\t\t.llseek = default_llseek,\n 444:\t};\n 445:\t\n 446:\tvoid nsim_tls_init(struct netdevsim *ns)\n 447:\t{\n 448:\t\tINIT_LIST_HEAD(\u0026ns-\u003etls.conns);\n 449:\t\tspin_lock_init(\u0026ns-\u003etls.lock);\n 450:\t\n 451:\t\tns-\u003enetdev-\u003etlsdev_ops = \u0026nsim_tlsdev_ops;\n 452:\t\tns-\u003enetdev-\u003efeatures |= NSIM_TLS_FEATURES;\n 453:\t\tns-\u003enetdev-\u003ehw_features |= NSIM_TLS_FEATURES;\n 454:\t\n 455:\t\tns-\u003etls.dfile = debugfs_create_file(\"tls\", 0400,\n 456:\t\t\t\t\t\t ns-\u003ensim_dev_port-\u003eddir, ns,\n 457:\t\t\t\t\t\t \u0026nsim_tls_dbg_fops);\n 458:\t}\n 459:\t\n 460:\tvoid nsim_tls_teardown(struct netdevsim *ns)\n 461:\t{\n 462:\t\tstruct nsim_tls_conn *conn, *tmp;\n 463:\t\tu32 left;\n 464:\t\n 465:\t\tdebugfs_remove_recursive(ns-\u003etls.dfile);\n 466:\t\n 467:\t\tspin_lock_bh(\u0026ns-\u003etls.lock);\n 468:\t\tleft = ns-\u003etls.count;\n 469:\t\tlist_for_each_entry_safe(conn, tmp, \u0026ns-\u003etls.conns, list) {\n 470:\t\t\tlist_del_rcu(\u0026conn-\u003elist);\n 471:\t\t\tkfree_rcu(conn, rcu);\n 472:\t\t}\n 473:\t\tns-\u003etls.count = 0;\n 474:\t\tns-\u003etls.tx_conn = 0;\n 475:\t\tns-\u003etls.rx_conn = 0;\n 476:\t\tspin_unlock_bh(\u0026ns-\u003etls.lock);\n 477:\t\n 478:\t\tif (left)\n 479:\t\t\tnetdev_err(ns-\u003enetdev,\n 480:\t\t\t\t \"tearing down TLS offload with %u connections left\\n\",\n 481:\t\t\t\t left);\n 482:\t}\n"
} |
| 19/2 | 2026/09/02 05:56 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 31136 output: 185 thoughts: 1212 Thoughts: **Analysis of the Netdevsim TLS Offload Emulation for Fuzzing** Alright, let's break this down. The patch introduces TLS offload emulation within the `netdevsim` driver, specifically targeting `drivers/net/netdevsim/tls.c`. It's hooking up transmit/receive functionality (`nsim_do_tls` into `nsim_start_xmit`), along with initialization and teardown routines (`nsim_init_netdevsim`, `nsim_destroy` for `nsim_tls_init` and `nsim_tls_teardown`). Crucially, it sets up `netdev->tlsdev_ops` with our custom TLS device operations: `nsim_tls_dev_add`, `nsim_tls_dev_del`, and `nsim_tls_dev_resync`. It also enables the `NETIF_F_HW_TLS_TX` and `NETIF_F_HW_TLS_RX` features. Now, is this prime fuzzing material? Absolutely. `netdevsim` is a godsend for emulating hardware offloads in virtualized setups, perfect for testing kernel networking subsystems like kTLS, BPF offload, MACsec, IPsec, etc. This adds a fully reachable software emulation of TLS device offload, including TX, RX, resync, debugging, and management, all runnable in QEMU or VMs, no special PCIe hardware needed. This is a great target. Next, the configuration requirements. We need to ensure `NETDEVSIM`, `TLS`, and `TLS_DEVICE` are enabled. Looking at the Kconfig files, the dependencies seem straightforward: `NETDEVSIM` needs `DEBUG_FS`, `INET`, and a few other options, while `TLS_DEVICE` depends on `TLS`, and `TLS` requires `INET`. The Makefile confirms this by including `tls.o` only if `CONFIG_TLS_DEVICE` is set. The header file further clarifies the conditional compilation. Let's dig into the symbols. The non-static functions are `nsim_tls_init`, `nsim_tls_teardown`, and `nsim_do_tls`. The callback functions are the non-hot-path entrypoints: `nsim_tls_dev_add`, `nsim_tls_dev_del`, `nsim_tls_dev_resync`, and `nsim_tls_dbg_read`. It's critical these callback functions aren't inlined because their addresses are used in `nsim_tlsdev_ops`. Therefore, the compiler has to expose them. Finally, we have our list for FocusSymbols. Following the instructions for FocusSymbols, we will avoid including hot-path functions. We want to list the following: `nsim_tls_init`, `nsim_tls_teardown`, `nsim_do_tls`, `nsim_tls_dev_add`, `nsim_tls_dev_del`, `nsim_tls_dev_resync`, and `nsim_tls_dbg_read`. In conclusion, fuzzing this is worthwhile because of its capability to emulate HW offload. The required configs are `NETDEVSIM`, `TLS`, and `TLS_DEVICE`. The symbols to focus on are the ones listed above. This is ready to be set-results. |
| 20/2 | 2026/09/02 05:57 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"NETDEVSIM",
"TLS",
"TLS_DEVICE"
],
"FocusSymbols": [
"nsim_tls_init",
"nsim_tls_teardown",
"nsim_do_tls",
"nsim_tls_dev_add",
"nsim_tls_dev_del",
"nsim_tls_dev_resync",
"nsim_tls_dbg_read"
],
"Reasoning": "The patch adds emulated kTLS hardware offload support to the netdevsim driver, including TLS device ops (add, del, resync), TX/RX packet handling and zeroing of authentication tags, and debugfs interface. This code runs in virtualized test environments and modifies reachable network device logic worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"NETDEVSIM",
"TLS",
"TLS_DEVICE"
],
"FocusSymbols": [
"nsim_tls_init",
"nsim_tls_teardown",
"nsim_do_tls",
"nsim_tls_dev_add",
"nsim_tls_dev_del",
"nsim_tls_dev_resync",
"nsim_tls_dbg_read"
],
"Reasoning": "The patch adds emulated kTLS hardware offload support to the netdevsim driver, including TLS device ops (add, del, resync), TX/RX packet handling and zeroing of authentication tags, and debugfs interface. This code runs in virtualized test environments and modifies reachable network device logic worth fuzzing.",
"WorthFuzzing": true
} |
| 21/1 | 2026/09/02 05:57 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 22/2 | 2026/09/02 05:57 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series implements emulated kTLS offload support for netdevsim. Analysis of memory initialization and handling across the patch shows:\n\n1. Memory allocation and initialization: All dynamic structures (such as `struct nsim_tls_conn` and debugfs read buffers) are allocated via `kzalloc_obj` or `kzalloc`, ensuring all struct fields are zero-initialized. List heads and spinlocks are explicitly initialized in `nsim_tls_init`.\n2. Stack variables: `struct nsim_tls_tuple` is fully populated by `nsim_tls_parse()` before any fields are read or compared in `nsim_tls_rx_offloaded()`.\n3. Userspace data transfer: The debugfs output in `nsim_tls_dbg_read()` uses `scnprintf()` into a zeroed buffer and copies exactly `p - buf` bytes via `simple_read_from_buffer()`.\n4. Packet tag handling: `nsim_tls_tx_zero_tags()` explicitly `memset`s the trailing record tag gap to zero to prevent sending uninitialized bytes across the simulated wire.\n\nPotential memory safety bugs in this driver extension (e.g., skb bounds, RCU lifetime management, concurrency, or list corruption) are detectable by KASAN, LOCKDEP, and standard sanitizers. There is no risk of uninitialized memory usage or info-leaks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 240c86524d0a06a0245d0c51e355f1d5f954fc6c
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 2 05:56:14 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ff79c466712df..cb4d4bb87afc2 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -607,6 +607,7 @@ config NETDEVSIM
depends on INET
depends on PSAMPLE || PSAMPLE=n
depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
+ depends on TLS || TLS=n
select NET_DEVLINK
select PAGE_POOL
select NET_SHAPER
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile
index 87718204fb4de..c7f7621323b2b 100644
--- a/drivers/net/netdevsim/Makefile
+++ b/drivers/net/netdevsim/Makefile
@@ -25,3 +25,7 @@ endif
ifneq ($(CONFIG_MACSEC),)
netdevsim-objs += macsec.o
endif
+
+ifneq ($(CONFIG_TLS_DEVICE),)
+netdevsim-objs += tls.o
+endif
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index b4a99f3ceac60..b9e0a2f5b18fc 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -151,6 +151,8 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
if (dr)
goto out_drop_free;
+ nsim_do_tls(skb, ns, peer_ns);
+
rxq = skb_get_queue_mapping(skb);
if (rxq >= peer_dev->num_rx_queues)
rxq = rxq % peer_dev->num_rx_queues;
@@ -1068,6 +1070,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
nsim_macsec_init(ns);
nsim_ipsec_init(ns);
+ nsim_tls_init(ns);
err = register_netdevice(ns->netdev);
if (err)
@@ -1095,6 +1098,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
RCU_INIT_POINTER(ns->peer, NULL);
unregister_netdevice(ns->netdev);
err_ipsec_teardown:
+ nsim_tls_teardown(ns);
nsim_ipsec_teardown(ns);
nsim_macsec_teardown(ns);
nsim_bpf_uninit(ns);
@@ -1197,6 +1201,7 @@ void nsim_destroy(struct netdevsim *ns)
RCU_INIT_POINTER(ns->peer, NULL);
unregister_netdevice(dev);
if (nsim_dev_port_is_pf(ns->nsim_dev_port)) {
+ nsim_tls_teardown(ns);
nsim_macsec_teardown(ns);
nsim_ipsec_teardown(ns);
nsim_bpf_uninit(ns);
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index 55aec41237b9b..b4646c7061bd0 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -56,6 +56,20 @@ struct nsim_ipsec {
u32 tx;
};
+struct nsim_tls {
+ struct list_head conns;
+ spinlock_t lock; /* protects conns and the counters below */
+ struct dentry *dfile;
+ u32 count;
+ u32 tx_conn;
+ u32 rx_conn;
+ atomic64_t tx_packets;
+ atomic64_t tx_bytes;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t resyncs;
+};
+
#define NSIM_MACSEC_MAX_SECY_COUNT 3
#define NSIM_MACSEC_MAX_RXSC_COUNT 1
struct nsim_rxsc {
@@ -141,6 +155,7 @@ struct netdevsim {
bool bpf_map_accept;
struct nsim_ipsec ipsec;
struct nsim_macsec macsec;
+ struct nsim_tls tls;
struct nsim_vlan vlan;
struct {
u32 inject_error;
@@ -434,6 +449,26 @@ static inline bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)
}
#endif
+#if IS_ENABLED(CONFIG_TLS_DEVICE)
+void nsim_tls_init(struct netdevsim *ns);
+void nsim_tls_teardown(struct netdevsim *ns);
+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns);
+#else
+static inline void nsim_tls_init(struct netdevsim *ns)
+{
+}
+
+static inline void nsim_tls_teardown(struct netdevsim *ns)
+{
+}
+
+static inline void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns)
+{
+}
+#endif
+
#if IS_ENABLED(CONFIG_MACSEC)
void nsim_macsec_init(struct netdevsim *ns);
void nsim_macsec_teardown(struct netdevsim *ns);
diff --git a/drivers/net/netdevsim/tls.c b/drivers/net/netdevsim/tls.c
new file mode 100644
index 0000000000000..079d30a86b5d6
--- /dev/null
+++ b/drivers/net/netdevsim/tls.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/ipv6.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/tcp.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/tcp.h>
+#include <net/tls.h>
+
+#include "netdevsim.h"
+
+/* Emulated kTLS offload. No crypto is performed: TX puts the record on the
+ * wire as plaintext and RX just marks it decrypted, so both ports have to
+ * have the offload installed on the connection before any data flows. That
+ * is the only supported setup. Anything else - a peer with no matching
+ * context, a context installed mid stream - has the core run the software
+ * AEAD over plaintext, which fails the connection with -EBADMSG.
+ */
+
+#define NSIM_TLS_MAX_CONN 32
+
+/* netdev_fix_features() drops NETIF_F_HW_TLS_RX unless the device also does
+ * RX checksums, which every offload capable NIC does.
+ */
+#define NSIM_TLS_FEATURES (NETIF_F_HW_TLS_TX | NETIF_F_HW_TLS_RX | \
+ NETIF_F_RXCSUM)
+
+struct nsim_tls_conn {
+ struct list_head list;
+ struct rcu_head rcu;
+
+ /* Identity as seen on the wire, from the point of view of the port
+ * the offload was installed on: l* is this side, r* is the peer.
+ */
+ struct in6_addr laddr;
+ struct in6_addr raddr;
+ __be16 lport;
+ __be16 rport;
+ u16 family;
+
+ enum tls_offload_ctx_dir dir;
+ u32 start_sn;
+ bool started;
+ u16 cipher_type;
+ const struct tls_context *tls_ctx;
+};
+
+struct nsim_tls_tuple {
+ struct in6_addr saddr;
+ struct in6_addr daddr;
+ __be16 sport;
+ __be16 dport;
+ u32 seq;
+};
+
+/* Parse the tuple from the frame the way a NIC would. The xmit skb is not
+ * always a well formed local TCP skb (AF_PACKET, tc redirect); use
+ * skb_header_pointer() so a non-linear or truncated header cannot be misread.
+ */
+static bool nsim_tls_parse(const struct sk_buff *skb,
+ struct nsim_tls_tuple *t)
+{
+ int off = skb_network_offset(skb);
+ const struct tcphdr *th;
+ struct tcphdr _th;
+
+ switch (skb->protocol) {
+ case htons(ETH_P_IP): {
+ const struct iphdr *iph;
+ struct iphdr _iph;
+
+ iph = skb_header_pointer(skb, off, sizeof(_iph), &_iph);
+ if (!iph || iph->version != 4 || iph->ihl < 5 ||
+ iph->protocol != IPPROTO_TCP || ip_is_fragment(iph))
+ return false;
+ ipv6_addr_set_v4mapped(iph->saddr, &t->saddr);
+ ipv6_addr_set_v4mapped(iph->daddr, &t->daddr);
+ off += iph->ihl * 4;
+ break;
+ }
+ case htons(ETH_P_IPV6): {
+ const struct ipv6hdr *ip6h;
+ struct ipv6hdr _ip6h;
+ __be16 frag_off;
+ u8 nexthdr;
+ int thoff;
+
+ ip6h = skb_header_pointer(skb, off, sizeof(_ip6h), &_ip6h);
+ if (!ip6h)
+ return false;
+
+ nexthdr = ip6h->nexthdr;
+ thoff = ipv6_skip_exthdr(skb, off + sizeof(_ip6h), &nexthdr,
+ &frag_off);
+ if (thoff < 0 || nexthdr != IPPROTO_TCP || frag_off)
+ return false;
+
+ t->saddr = ip6h->saddr;
+ t->daddr = ip6h->daddr;
+ off = thoff;
+ break;
+ }
+ default:
+ return false;
+ }
+
+ th = skb_header_pointer(skb, off, sizeof(_th), &_th);
+ if (!th)
+ return false;
+ t->sport = th->source;
+ t->dport = th->dest;
+ t->seq = ntohl(th->seq);
+
+ return true;
+}
+
+/* No socket on RX yet, so match the tuple installed at ->tls_dev_add(), the
+ * way the hardware does.
+ */
+static bool nsim_tls_rx_offloaded(struct netdevsim *ns,
+ const struct nsim_tls_tuple *t)
+{
+ struct nsim_tls_conn *conn;
+
+ list_for_each_entry_rcu(conn, &ns->tls.conns, list) {
+ if (conn->dir != TLS_OFFLOAD_CTX_DIR_RX ||
+ conn->lport != t->dport || conn->rport != t->sport ||
+ !ipv6_addr_equal(&conn->laddr, &t->daddr) ||
+ !ipv6_addr_equal(&conn->raddr, &t->saddr))
+ continue;
+
+ /* Anything before start_sn predates the offload and has to
+ * stay encrypted. Only worth asking once: TCP sequence
+ * numbers wrap, and a stream that has moved 2G past a
+ * sequence it is still compared against looks like it went
+ * backwards.
+ */
+ if (!READ_ONCE(conn->started)) {
+ if (before(t->seq, conn->start_sn))
+ return false;
+ WRITE_ONCE(conn->started, true);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/* The stack leaves an uninitialized, tag sized gap at the tail of each record
+ * for the device to write the auth tag into. We do not encrypt, so zero it
+ * to avoid leaking that memory onto the wire. Nothing tells the device where
+ * the tag is, so walk the records with tls_get_record() and clear the end of
+ * each one's last frag.
+ */
+static void nsim_tls_tx_zero_tags(struct sk_buff *skb)
+{
+ struct tls_context *ctx = tls_get_ctx(skb->sk);
+ const struct tcphdr *th = tcp_hdr(skb);
+ struct tls_offload_context_tx *tx_ctx;
+ u32 seq, end, tag_size;
+ unsigned long flags;
+ unsigned int off;
+
+ off = skb_transport_offset(skb) + __tcp_hdrlen(th);
+ if (off >= skb->len)
+ return;
+
+ tag_size = ctx->prot_info.tag_size;
+ seq = ntohl(th->seq);
+ end = seq + skb->len - off;
+ tx_ctx = tls_offload_ctx_tx(ctx);
+
+ spin_lock_irqsave(&tx_ctx->lock, flags);
+ while (before(seq, end)) {
+ struct tls_record_info *record;
+ skb_frag_t *frag;
+ u64 rcd_sn;
+
+ record = tls_get_record(tx_ctx, seq, &rcd_sn);
+ if (!record || tls_record_is_start_marker(record))
+ break;
+
+ frag = &record->frags[record->num_frags - 1];
+ memset(skb_frag_address(frag) + skb_frag_size(frag) - tag_size,
+ 0, tag_size);
+
+ seq = record->end_seq;
+ }
+ spin_unlock_irqrestore(&tx_ctx->lock, flags);
+}
+
+/* Stand in for the inline encryption the hardware would do on the way out.
+ * The stack has already framed the record and we do not encrypt, so the tag
+ * is all that is left to deal with. The socket is right here, so identify
+ * the connection the way the real drivers do.
+ */
+static bool nsim_tls_tx(struct netdevsim *ns, struct sk_buff *skb)
+{
+ if (!tls_is_skb_tx_device_offloaded(skb))
+ return false;
+
+ /* The question tls_validate_xmit_skb() asks: on anything other than
+ * the device the context is attached to, the core has already
+ * encrypted the record in software, so it is real ciphertext and none
+ * of our business.
+ */
+ if (rcu_dereference_bh(tls_get_ctx(skb->sk)->netdev) != ns->netdev)
+ return false;
+
+ nsim_tls_tx_zero_tags(skb);
+ atomic64_inc(&ns->tls.tx_packets);
+ atomic64_add(skb->len, &ns->tls.tx_bytes);
+
+ return true;
+}
+
+/* Stand in for the inline decryption the peer's hardware would do, since we
+ * are about to hand the skb to its stack. We do not decrypt either, so all
+ * that is needed is the flag that tells the kTLS core the payload is
+ * already plaintext.
+ */
+static void nsim_tls_rx(struct netdevsim *ns, struct sk_buff *skb)
+{
+ struct nsim_tls_tuple t;
+
+ /* Not every port registers the offload, and one that does not never
+ * initializes the list head, so gate on the count. It is the
+ * cheaper test anyway.
+ */
+ if (!READ_ONCE(ns->tls.count))
+ return;
+
+ if (!nsim_tls_parse(skb, &t))
+ return;
+
+ if (!nsim_tls_rx_offloaded(ns, &t))
+ return;
+
+ skb->decrypted = 1;
+ atomic64_inc(&ns->tls.rx_packets);
+ atomic64_add(skb->len, &ns->tls.rx_bytes);
+}
+
+/* netdevsim has no wire: nsim_start_xmit() hands the skb straight to the
+ * peer's receive path, so this one call site stands in for the hardware of
+ * both ports. It has to run before nsim_forward_skb() moves the skb over,
+ * which resets the headers.
+ */
+void nsim_do_tls(struct sk_buff *skb, struct netdevsim *ns,
+ struct netdevsim *peer_ns)
+{
+ /* nsim_do_psp() ran first and may have wrapped the record stream, so
+ * this is no longer a plain TLS over TCP packet. No NIC chains the
+ * two inline offloads either, so leave it alone.
+ */
+ if (skb->encapsulation)
+ return;
+
+ /* Only what this port just "encrypted" may skip decryption on the
+ * peer. Everything else - a software kTLS sender because the TX
+ * feature is off, a frame injected on the tuple - really is
+ * ciphertext or not TLS at all, and the core has to deal with it.
+ */
+ if (!nsim_tls_tx(ns, skb))
+ return;
+
+ nsim_tls_rx(peer_ns, skb);
+}
+
+static int nsim_tls_dev_add(struct net_device *netdev, struct sock *sk,
+ enum tls_offload_ctx_dir direction,
+ struct tls_crypto_info *crypto_info,
+ u32 start_offload_tcp_sn)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+ struct nsim_tls_conn *conn;
+ int ret = 0;
+
+ /* Mirror the cipher support of a typical offload capable NIC. */
+ switch (crypto_info->cipher_type) {
+ case TLS_CIPHER_AES_GCM_128:
+ case TLS_CIPHER_AES_GCM_256:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ conn = kzalloc_obj(*conn);
+ if (!conn)
+ return -ENOMEM;
+
+ if (sk->sk_family == AF_INET6) {
+ conn->laddr = sk->sk_v6_rcv_saddr;
+ conn->raddr = sk->sk_v6_daddr;
+ } else {
+ ipv6_addr_set_v4mapped(sk->sk_rcv_saddr, &conn->laddr);
+ ipv6_addr_set_v4mapped(sk->sk_daddr, &conn->raddr);
+ }
+ conn->family = sk->sk_family;
+ conn->lport = htons(inet_sk(sk)->inet_num);
+ conn->rport = sk->sk_dport;
+ conn->dir = direction;
+ conn->start_sn = start_offload_tcp_sn;
+ conn->cipher_type = crypto_info->cipher_type;
+ conn->tls_ctx = tls_get_ctx(sk);
+
+ spin_lock_bh(&ns->tls.lock);
+ if (ns->tls.count >= NSIM_TLS_MAX_CONN) {
+ ret = -ENOSPC;
+ goto out_unlock;
+ }
+ list_add_tail_rcu(&conn->list, &ns->tls.conns);
+ ns->tls.count++;
+ if (direction == TLS_OFFLOAD_CTX_DIR_TX)
+ ns->tls.tx_conn++;
+ else
+ ns->tls.rx_conn++;
+out_unlock:
+ spin_unlock_bh(&ns->tls.lock);
+
+ if (ret)
+ kfree(conn);
+
+ return ret;
+}
+
+static void nsim_tls_dev_del(struct net_device *netdev,
+ struct tls_context *tls_ctx,
+ enum tls_offload_ctx_dir direction)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+ struct nsim_tls_conn *conn;
+
+ spin_lock_bh(&ns->tls.lock);
+ list_for_each_entry(conn, &ns->tls.conns, list) {
+ if (conn->tls_ctx != tls_ctx || conn->dir != direction)
+ continue;
+
+ list_del_rcu(&conn->list);
+ ns->tls.count--;
+ if (direction == TLS_OFFLOAD_CTX_DIR_TX)
+ ns->tls.tx_conn--;
+ else
+ ns->tls.rx_conn--;
+ spin_unlock_bh(&ns->tls.lock);
+
+ kfree_rcu(conn, rcu);
+ return;
+ }
+ spin_unlock_bh(&ns->tls.lock);
+
+ netdev_err(netdev, "TLS %s context not found on del\n",
+ direction == TLS_OFFLOAD_CTX_DIR_TX ? "tx" : "rx");
+}
+
+/* Nothing is ever out of sync here: the emulation does not decrypt, so it
+ * never loses the record boundaries and never asks for a resync. The core
+ * can still call in on the RX path, so account for it and move on.
+ */
+static int nsim_tls_dev_resync(struct net_device *netdev, struct sock *sk,
+ u32 seq, u8 *rcd_sn,
+ enum tls_offload_ctx_dir direction)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+
+ atomic64_inc(&ns->tls.resyncs);
+
+ return 0;
+}
+
+static const struct tlsdev_ops nsim_tlsdev_ops = {
+ .tls_dev_add = nsim_tls_dev_add,
+ .tls_dev_del = nsim_tls_dev_del,
+ .tls_dev_resync = nsim_tls_dev_resync,
+};
+
+static ssize_t nsim_tls_dbg_read(struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct netdevsim *ns = filp->private_data;
+ struct nsim_tls_conn *conn;
+ size_t bufsize;
+ char *buf, *p;
+ int len;
+
+ /* Two full IPv6 addresses and ports fit in 160 bytes a line. */
+ bufsize = (NSIM_TLS_MAX_CONN * 160) + 200;
+ buf = kzalloc(bufsize, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ p = buf;
+
+ spin_lock_bh(&ns->tls.lock);
+ p += scnprintf(p, bufsize - (p - buf),
+ "conn count=%u tx=%u rx=%u\n",
+ ns->tls.count, ns->tls.tx_conn, ns->tls.rx_conn);
+ p += scnprintf(p, bufsize - (p - buf),
+ "tx_packets=%llu tx_bytes=%llu rx_packets=%llu rx_bytes=%llu resyncs=%llu\n",
+ atomic64_read(&ns->tls.tx_packets),
+ atomic64_read(&ns->tls.tx_bytes),
+ atomic64_read(&ns->tls.rx_packets),
+ atomic64_read(&ns->tls.rx_bytes),
+ atomic64_read(&ns->tls.resyncs));
+
+ list_for_each_entry(conn, &ns->tls.conns, list) {
+ if (conn->family == AF_INET6)
+ p += scnprintf(p, bufsize - (p - buf),
+ "%s [%pI6c]:%u -> [%pI6c]:%u cipher=%u sn=%u\n",
+ conn->dir == TLS_OFFLOAD_CTX_DIR_TX ?
+ "tx" : "rx",
+ &conn->laddr, ntohs(conn->lport),
+ &conn->raddr, ntohs(conn->rport),
+ conn->cipher_type, conn->start_sn);
+ else
+ p += scnprintf(p, bufsize - (p - buf),
+ "%s %pI4:%u -> %pI4:%u cipher=%u sn=%u\n",
+ conn->dir == TLS_OFFLOAD_CTX_DIR_TX ?
+ "tx" : "rx",
+ &conn->laddr.s6_addr32[3],
+ ntohs(conn->lport),
+ &conn->raddr.s6_addr32[3],
+ ntohs(conn->rport),
+ conn->cipher_type, conn->start_sn);
+ }
+ spin_unlock_bh(&ns->tls.lock);
+
+ len = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);
+
+ kfree(buf);
+
+ return len;
+}
+
+static const struct file_operations nsim_tls_dbg_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = nsim_tls_dbg_read,
+ .llseek = default_llseek,
+};
+
+void nsim_tls_init(struct netdevsim *ns)
+{
+ INIT_LIST_HEAD(&ns->tls.conns);
+ spin_lock_init(&ns->tls.lock);
+
+ ns->netdev->tlsdev_ops = &nsim_tlsdev_ops;
+ ns->netdev->features |= NSIM_TLS_FEATURES;
+ ns->netdev->hw_features |= NSIM_TLS_FEATURES;
+
+ ns->tls.dfile = debugfs_create_file("tls", 0400,
+ ns->nsim_dev_port->ddir, ns,
+ &nsim_tls_dbg_fops);
+}
+
+void nsim_tls_teardown(struct netdevsim *ns)
+{
+ struct nsim_tls_conn *conn, *tmp;
+ u32 left;
+
+ debugfs_remove_recursive(ns->tls.dfile);
+
+ spin_lock_bh(&ns->tls.lock);
+ left = ns->tls.count;
+ list_for_each_entry_safe(conn, tmp, &ns->tls.conns, list) {
+ list_del_rcu(&conn->list);
+ kfree_rcu(conn, rcu);
+ }
+ ns->tls.count = 0;
+ ns->tls.tx_conn = 0;
+ ns->tls.rx_conn = 0;
+ spin_unlock_bh(&ns->tls.lock);
+
+ if (left)
+ netdev_err(ns->netdev,
+ "tearing down TLS offload with %u connections left\n",
+ left);
+}
diff --git a/tools/testing/selftests/drivers/net/netdevsim/.gitignore b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
new file mode 100644
index 0000000000000..9d4f57cb2baa9
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+tls_offload
diff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools/testing/selftests/drivers/net/netdevsim/Makefile
index 9808c2fbae9e8..5beb4ed64eb52 100644
--- a/tools/testing/selftests/drivers/net/netdevsim/Makefile
+++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0+ OR MIT
+CFLAGS += $(KHDR_INCLUDES)
+
TEST_PROGS := \
devlink.sh \
devlink_in_netns.sh \
@@ -15,9 +17,14 @@ TEST_PROGS := \
peer.sh \
psample.sh \
tc-mq-visibility.sh \
+ tls.sh \
udp_tunnel_nic.sh \
# end of TEST_PROGS
+TEST_GEN_FILES := \
+ tls_offload
+# end of TEST_GEN_FILES
+
TEST_FILES := \
ethtool-common.sh
# end of TEST_FILES
diff --git a/tools/testing/selftests/drivers/net/netdevsim/config b/tools/testing/selftests/drivers/net/netdevsim/config
index 5117c78ddf0af..5893111ba1363 100644
--- a/tools/testing/selftests/drivers/net/netdevsim/config
+++ b/tools/testing/selftests/drivers/net/netdevsim/config
@@ -8,4 +8,6 @@ CONFIG_NET_SCH_MULTIQ=y
CONFIG_NET_SCH_PRIO=y
CONFIG_PSAMPLE=y
CONFIG_PTP_1588_CLOCK_MOCK=y
+CONFIG_TLS=m
+CONFIG_TLS_DEVICE=y
CONFIG_VXLAN=m
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls.sh b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
new file mode 100755
index 0000000000000..59b7d1c0e5148
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
@@ -0,0 +1,334 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Exercise netdevsim's emulated kTLS device offload over a linked
+# netdevsim pair, one port per network namespace.
+#
+# shellcheck disable=SC2154 # ksft_skip comes from lib.sh
+
+lib_dir=$(dirname "$0")
+# shellcheck source=./../../../net/lib.sh
+# shellcheck disable=SC1091
+source "$lib_dir"/../../../net/lib.sh
+
+# Device IDs and the nssv/nscl namespaces are picked the same way as peer.sh.
+NSIM_DEV_1_ID=$((256 + RANDOM % 256))
+NSIM_DEV_1_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_1_ID
+NSIM_DEV_2_ID=$((512 + RANDOM % 256))
+NSIM_DEV_2_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_2_ID
+
+NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device
+NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device
+NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device
+
+DEBUGFS=/sys/kernel/debug/netdevsim
+NSIM_DEV_1_TLS=$DEBUGFS/netdevsim$NSIM_DEV_1_ID/ports/0/tls
+NSIM_DEV_2_TLS=$DEBUGFS/netdevsim$NSIM_DEV_2_ID/ports/0/tls
+
+SRV_IP=192.168.13.1
+CLI_IP=192.168.13.2
+PORT=4433
+
+SYNCDIR=
+BIN=$lib_dir/tls_offload
+
+# The helpers allow themselves 20s to connect and 20s to meet at the barrier.
+READY_TIMEOUT_SEC=30
+
+num_pass=0
+num_fail=0
+
+check()
+{
+ local msg="$1"
+ local ret="$2"
+
+ if [ "$ret" -eq 0 ]; then
+ echo "PASS: $msg"
+ num_pass=$((num_pass + 1))
+ else
+ echo "FAIL: $msg"
+ num_fail=$((num_fail + 1))
+ fi
+}
+
+# defer takes a command, not a redirection.
+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope
+nsim_dev_del()
+{
+ echo "$1" > "$NSIM_DEV_SYS_DEL"
+}
+
+# Already reaped on the normal path, so ignore both failures.
+# shellcheck disable=SC2317,SC2329 # invoked from a defer scope
+kill_wait()
+{
+ kill "$1" 2>/dev/null
+ wait "$1" 2>/dev/null
+ return 0
+}
+
+# Each resource is handed to defer only once it exists, so a run that loses a
+# race for one of the global names does not tear down the winner's.
+setup()
+{
+ set -e
+
+ echo "$NSIM_DEV_1_ID" > "$NSIM_DEV_SYS_NEW"
+ defer nsim_dev_del "$NSIM_DEV_1_ID"
+ echo "$NSIM_DEV_2_ID" > "$NSIM_DEV_SYS_NEW"
+ defer nsim_dev_del "$NSIM_DEV_2_ID"
+ udevadm settle 2>/dev/null || sleep 1
+
+ NSIM_DEV_1_NAME=$(find "$NSIM_DEV_1_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_1_SYS"/net -exec basename {} \;)
+ NSIM_DEV_2_NAME=$(find "$NSIM_DEV_2_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_2_SYS"/net -exec basename {} \;)
+
+ ip netns add nssv
+ defer ip netns del nssv
+ ip netns add nscl
+ defer ip netns del nscl
+
+ ip link set "$NSIM_DEV_1_NAME" netns nssv
+ ip link set "$NSIM_DEV_2_NAME" netns nscl
+
+ ip netns exec nssv ip addr add "$SRV_IP/24" dev "$NSIM_DEV_1_NAME"
+ ip netns exec nscl ip addr add "$CLI_IP/24" dev "$NSIM_DEV_2_NAME"
+
+ ip netns exec nssv ip link set dev "$NSIM_DEV_1_NAME" up
+ ip netns exec nscl ip link set dev "$NSIM_DEV_2_NAME" up
+
+ NSIM_DEV_1_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_1_FD}</var/run/netns/nssv
+ NSIM_DEV_1_IFIDX=$(ip netns exec nssv \
+ cat /sys/class/net/"$NSIM_DEV_1_NAME"/ifindex)
+
+ NSIM_DEV_2_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_2_FD}</var/run/netns/nscl
+ NSIM_DEV_2_IFIDX=$(ip netns exec nscl \
+ cat /sys/class/net/"$NSIM_DEV_2_NAME"/ifindex)
+
+ echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX" \
+ > "$NSIM_DEV_SYS_LINK"
+
+ SYNCDIR=$(mktemp -d)
+ defer rm -rf "$SYNCDIR"
+ set +e
+}
+
+feature()
+{
+ local netns="$1"
+ local dev="$2"
+ local feat="$3"
+
+ ip netns exec "$netns" ethtool -k "$dev" 2>/dev/null | \
+ sed -n "s/^$feat: \([a-z]*\).*/\1/p"
+}
+
+dbg_field()
+{
+ sed -n "s/.*\<$2=\([0-9]*\).*/\1/p" "$1" | head -1
+}
+
+tls_stat()
+{
+ ip netns exec "$1" cat /proc/net/tls_stat | \
+ awk -v name="$2" '$1 == name {print $2}'
+}
+
+# shellcheck disable=SC2317,SC2329 # invoked by name from slowwait
+both_ready()
+{
+ [ -e "$SYNCDIR/server.ready" ] && [ -e "$SYNCDIR/client.ready" ]
+}
+
+# Both ends park once their offload is installed and before any data is
+# sent, so the driver's context count can be sampled without racing the
+# transfer. Pass "nosample" when the offload is expected to be refused,
+# since then neither end ever reaches the barrier.
+run_pair()
+{
+ local sample="${1:-sample}"
+ local srv_rc cli_rc ready=1
+
+ rm -f "$SYNCDIR"/*.ready "$SYNCDIR"/go
+ CONNS_1=0
+ CONNS_2=0
+
+ defer_scope_push
+
+ ip netns exec nssv "$BIN" server "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local srv_pid=$!
+ defer kill_wait "$srv_pid"
+ ip netns exec nscl "$BIN" client "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local cli_pid=$!
+ defer kill_wait "$cli_pid"
+
+ if [ "$sample" = "sample" ]; then
+ # Comfortably longer than the helpers take to get to the
+ # barrier. Timing out here means a sample would be stale, so
+ # fail the run rather than assert on it.
+ slowwait "$READY_TIMEOUT_SEC" both_ready >/dev/null || ready=0
+ if [ "$ready" -eq 1 ]; then
+ CONNS_1=$(dbg_field "$NSIM_DEV_1_TLS" count)
+ CONNS_2=$(dbg_field "$NSIM_DEV_2_TLS" count)
+ : "${CONNS_1:=0}"
+ : "${CONNS_2:=0}"
+ fi
+ fi
+ touch "$SYNCDIR/go"
+
+ wait "$srv_pid"; srv_rc=$?
+ wait "$cli_pid"; cli_rc=$?
+
+ defer_scope_pop
+
+ [ "$ready" -eq 1 ] && [ "$srv_rc" -eq 0 ] && [ "$cli_rc" -eq 0 ]
+}
+
+###
+### Code start
+###
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: need root"
+ exit "$ksft_skip"
+fi
+
+if ! command -v ethtool >/dev/null; then
+ echo "SKIP: ethtool not found"
+ exit "$ksft_skip"
+fi
+
+if [ ! -x "$BIN" ]; then
+ echo "SKIP: $BIN not built"
+ exit "$ksft_skip"
+fi
+
+modprobe netdevsim 2>/dev/null
+if [ ! -d /sys/bus/netdevsim ]; then
+ echo "SKIP: netdevsim not available"
+ exit "$ksft_skip"
+fi
+
+modprobe tls 2>/dev/null
+if [ ! -e /proc/net/tls_stat ]; then
+ echo "SKIP: kernel TLS not available"
+ exit "$ksft_skip"
+fi
+
+trap defer_scopes_cleanup EXIT
+setup
+
+# /proc/net/tls_stat only proves generic kTLS. Without CONFIG_TLS_DEVICE the
+# netdevsim TLS hooks are stubbed out: no offload features and no per-port tls
+# debugfs file. That is an unsupported configuration for this test, not a
+# failure - skip it.
+if [ ! -e "$NSIM_DEV_1_TLS" ]; then
+ echo "SKIP: netdevsim built without TLS device offload (CONFIG_TLS_DEVICE=n)"
+ exit "$ksft_skip"
+fi
+
+# The offload has to be advertised, and on by default like the other
+# netdevsim crypto offloads.
+for f in tls-hw-tx-offload tls-hw-rx-offload; do
+ [ "$(feature nssv "$NSIM_DEV_1_NAME" "$f")" = "on" ]
+ check "$f advertised and on by default" $?
+done
+
+[ -e "$NSIM_DEV_1_TLS" ]
+check "per-port debugfs tls file exists" $?
+
+# Main data path run.
+run_pair
+check "offloaded TLS data transfer" $?
+
+# Sampled at the barrier, so each port must be holding exactly the TX and
+# the RX context of its own socket.
+[ "$CONNS_1" -eq 2 ] && [ "$CONNS_2" -eq 2 ]
+check "tx and rx contexts installed on both ports" $?
+
+# Both ends must have gone through the device path, not the SW fallback.
+[ "$(tls_stat nssv TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nssv TlsRxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsRxDevice)" -ge 1 ]
+check "both ends used the device path" $?
+
+[ "$(tls_stat nssv TlsTxSw)" -eq 0 ] && [ "$(tls_stat nscl TlsTxSw)" -eq 0 ]
+check "no silent fallback to the software path" $?
+
+[ "$(tls_stat nssv TlsDecryptError)" -eq 0 ] && \
+ [ "$(tls_stat nscl TlsDecryptError)" -eq 0 ]
+check "no decrypt errors" $?
+
+# The driver must have seen the records go by in both directions.
+[ "$(dbg_field "$NSIM_DEV_1_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_1_TLS" rx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" rx_packets)" -ge 1 ]
+check "driver counted offloaded packets both ways" $?
+
+# Sockets are closed by now, so every context must have been given back.
+# TX contexts are torn down from a workqueue after the socket is destroyed
+# (tls_device_sk_destruct() -> tls_device_tx_del_task), so tls_dev_del() may
+# not have run yet - poll rather than sample once.
+# shellcheck disable=SC2317,SC2329 # invoked by name from busywait
+all_ctx_released()
+{
+ [ "$(dbg_field "$NSIM_DEV_1_TLS" count)" -eq 0 ] &&
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" count)" -eq 0 ]
+}
+
+busywait 2000 all_ctx_released >/dev/null
+check "all offload contexts released on close" $?
+
+[ "$(tls_stat nssv TlsCurrTxDevice)" -eq 0 ] && \
+ [ "$(tls_stat nssv TlsCurrRxDevice)" -eq 0 ]
+check "no device contexts left behind" $?
+
+set_features()
+{
+ local want="$1"
+ local ret=0
+ local f
+
+ for f in tls-hw-tx-offload tls-hw-rx-offload; do
+ ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" "$f" "$want"
+ ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" "$f" "$want"
+
+ [ "$(feature nssv "$NSIM_DEV_1_NAME" "$f")" = "$want" ] || ret=1
+ [ "$(feature nscl "$NSIM_DEV_2_NAME" "$f")" = "$want" ] || ret=1
+ done
+
+ return "$ret"
+}
+
+# Turning the features off has to make the offload refuse the connection;
+# the test binary insists on the device path, so it must now fail.
+set_features off
+check "both offload features turned off on both ports" $?
+
+run_pair nosample
+rc=$?
+[ "$rc" -ne 0 ]
+check "offload declined once the feature is off" $?
+
+# Both directions on both ends have to have landed on the software path.
+[ "$(tls_stat nssv TlsTxSw)" -ge 1 ] && [ "$(tls_stat nssv TlsRxSw)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsTxSw)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsRxSw)" -ge 1 ]
+check "software path used when offload is off" $?
+
+set_features on
+check "both offload features turned back on" $?
+
+run_pair
+check "offload works again after re-enabling" $?
+
+echo
+echo "passed: $num_pass failed: $num_fail"
+[ "$num_fail" -eq 0 ] && exit 0
+exit 1
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
new file mode 100644
index 0000000000000..0d7c8950ca6ce
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kTLS device offload data path exercise, driven by tls.sh.
+ *
+ * One instance runs as the server and one as the client, each in its own
+ * network namespace, connected back to back by a linked netdevsim pair.
+ * Both ends enable kTLS and rely on netdevsim's emulated TLS offload, so
+ * every record travels through net/tls/tls_device.c rather than the
+ * software path.
+ *
+ * The two processes rendezvous through a shared directory so that neither
+ * side sends before the other has installed its RX offload.
+ */
+
+#define _GNU_SOURCE
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/tls.h>
+
+#ifndef SOL_TLS
+#define SOL_TLS 282
+#endif
+
+#ifndef TCP_ULP
+#define TCP_ULP 31
+#endif
+
+#define BULK_LEN (200 * 1024)
+#define MORE_FRAGS 64
+#define SPLICE_FRAG_LEN 4096
+#define SPLICE_FRAGS 8
+
+/* TLS_MIN_RECORD_SIZE_LIM and TLS_MAX_PAYLOAD_SIZE, which are not uapi. */
+#define REC_LIM_MIN 64
+#define REC_LIM_MAX 16384
+
+#define SMALL_RECS 100
+#define SMALL_LEN (REC_LIM_MIN * SMALL_RECS)
+
+#define SYNC_TIMEOUT_MS 20000
+#define CONNECT_TIMEOUT_MS 20000
+
+/* accept() and the transfers have no timeout of their own, and tls.sh waits
+ * on both helpers. Bound the whole run so a stuck peer cannot hang the test.
+ */
+#define RUN_TIMEOUT_SEC 120
+
+static const char *role;
+
+static void die(const char *what)
+{
+ fprintf(stderr, "%s: %s: %s\n", role, what, strerror(errno));
+ exit(1);
+}
+
+static void fail(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", role);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void msleep(unsigned int ms)
+{
+ struct timespec ts = {
+ .tv_sec = ms / 1000,
+ .tv_nsec = (ms % 1000) * 1000000L,
+ };
+
+ nanosleep(&ts, NULL);
+}
+
+/* /proc/net/tls_stat is per netns, so both ends can check that their own
+ * connection really landed on the device path.
+ */
+static unsigned long read_tls_stat(const char *name)
+{
+ char line[256];
+ unsigned long val;
+ FILE *f;
+
+ f = fopen("/proc/net/tls_stat", "r");
+ if (!f)
+ die("open /proc/net/tls_stat");
+
+ while (fgets(line, sizeof(line), f)) {
+ char key[64];
+
+ if (sscanf(line, "%63s %lu", key, &val) != 2)
+ continue;
+ if (!strcmp(key, name)) {
+ fclose(f);
+ return val;
+ }
+ }
+
+ fclose(f);
+ fail("%s not found in /proc/net/tls_stat", name);
+ return 0;
+}
+
+static void fill_pattern(char *buf, size_t len, unsigned int seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = (char)(seed + i * 31 + (i >> 8) * 7);
+}
+
+static void check_pattern(const char *buf, size_t len, unsigned int seed,
+ const char *what)
+{
+ char *want = malloc(len);
+ size_t i;
+
+ if (!want)
+ die("malloc");
+
+ fill_pattern(want, len, seed);
+ for (i = 0; i < len; i++) {
+ if (buf[i] != want[i])
+ fail("%s: payload mismatch at byte %zu: got 0x%02x want 0x%02x",
+ what, i, (unsigned char)buf[i],
+ (unsigned char)want[i]);
+ }
+
+ free(want);
+}
+
+static void write_all(int fd, const char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = send(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("send");
+ }
+ done += n;
+ }
+}
+
+static void read_all(int fd, char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = recv(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("recv");
+ }
+ if (n == 0)
+ fail("peer closed after %zu of %zu bytes", done, len);
+ done += n;
+ }
+}
+
+static void enable_ktls(int fd)
+{
+ struct tls12_crypto_info_aes_gcm_128 ci = {};
+ unsigned long tx_before, rx_before;
+
+ tx_before = read_tls_stat("TlsTxDevice");
+ rx_before = read_tls_stat("TlsRxDevice");
+
+ if (setsockopt(fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls")))
+ die("setsockopt(TCP_ULP, tls)");
+
+ ci.info.version = TLS_1_2_VERSION;
+ ci.info.cipher_type = TLS_CIPHER_AES_GCM_128;
+ memset(ci.iv, 'i', sizeof(ci.iv));
+ memset(ci.key, 'k', sizeof(ci.key));
+ memset(ci.salt, 's', sizeof(ci.salt));
+ memset(ci.rec_seq, 0, sizeof(ci.rec_seq));
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_TX)");
+ if (setsockopt(fd, SOL_TLS, TLS_RX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_RX)");
+
+ /* The whole point of the exercise: refuse to silently fall back to
+ * the software path, otherwise the test would pass without ever
+ * touching tls_device.c.
+ */
+ if (read_tls_stat("TlsTxDevice") != tx_before + 1)
+ fail("TX did not land on the device path (TlsTxDevice %lu -> %lu)",
+ tx_before, read_tls_stat("TlsTxDevice"));
+ if (read_tls_stat("TlsRxDevice") != rx_before + 1)
+ fail("RX did not land on the device path (TlsRxDevice %lu -> %lu)",
+ rx_before, read_tls_stat("TlsRxDevice"));
+}
+
+static void sync_path(char *out, size_t len, const char *dir, const char *who)
+{
+ if ((size_t)snprintf(out, len, "%s/%s.ready", dir, who) >= len)
+ fail("sync dir path too long");
+}
+
+static void rendezvous(const char *dir, const char *me, const char *peer)
+{
+ char mine[PATH_MAX], theirs[PATH_MAX];
+ unsigned int waited = 0;
+ int fd;
+
+ sync_path(mine, sizeof(mine), dir, me);
+ sync_path(theirs, sizeof(theirs), dir, peer);
+
+ fd = open(mine, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0)
+ die("create sync file");
+ close(fd);
+
+ while (access(theirs, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for %s", peer);
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Both ends stop here with their offload installed and no data sent yet,
+ * so that the driver state can be inspected from the outside.
+ */
+static void wait_for_go(const char *dir)
+{
+ unsigned int waited = 0;
+ char go[PATH_MAX];
+
+ if ((size_t)snprintf(go, sizeof(go), "%s/go", dir) >= sizeof(go))
+ fail("sync dir path too long");
+
+ while (access(go, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for go");
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Small writes with MSG_MORE accumulate into one open record before it is
+ * pushed, which is the interesting part of tls_push_data().
+ */
+static void send_msg_more(int fd, unsigned int seed)
+{
+ char buf[MORE_FRAGS + 1];
+ int i;
+
+ fill_pattern(buf, sizeof(buf), seed);
+
+ for (i = 0; i < MORE_FRAGS; i++) {
+ if (send(fd, buf + i, 1, MSG_MORE) != 1)
+ die("send(MSG_MORE)");
+ }
+ if (send(fd, buf + MORE_FRAGS, 1, 0) != 1)
+ die("send(last)");
+}
+
+/* splice() reaches tls_push_data() with MSG_SPLICE_PAGES once
+ * TLS_TX_ZEROCOPY_RO is enabled, which is a distinct fragment path.
+ */
+static void send_splice(int fd, unsigned int seed)
+{
+ char buf[SPLICE_FRAG_LEN];
+ int val = 1;
+ int i;
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO)");
+
+ for (i = 0; i < SPLICE_FRAGS; i++) {
+ int p[2];
+
+ fill_pattern(buf, sizeof(buf), seed + i * SPLICE_FRAG_LEN);
+
+ if (pipe(p))
+ die("pipe");
+ if (write(p[1], buf, sizeof(buf)) != sizeof(buf))
+ die("write to pipe");
+ if (splice(p[0], NULL, fd, NULL, sizeof(buf),
+ i == SPLICE_FRAGS - 1 ? 0 : SPLICE_F_MORE) !=
+ sizeof(buf))
+ die("splice");
+ close(p[0]);
+ close(p[1]);
+ }
+
+ val = 0;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO off)");
+}
+
+/* A record can be up to 16K, so normally one segment carries a piece of a
+ * single record. Shrinking the limit puts a dozen or so whole records in
+ * every segment instead, which is the multi-record path through the driver.
+ */
+static void send_small_records(int fd, unsigned int seed)
+{
+ char buf[SMALL_LEN];
+ uint16_t limit;
+
+ limit = REC_LIM_MIN;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN)");
+
+ fill_pattern(buf, sizeof(buf), seed);
+ write_all(fd, buf, sizeof(buf));
+
+ limit = REC_LIM_MAX;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN restore)");
+}
+
+#define SEED_C2S_BULK 0x11
+#define SEED_S2C_BULK 0x22
+#define SEED_C2S_MORE 0x33
+#define SEED_C2S_SPLICE 0x44
+#define SEED_C2S_SMALL 0x55
+
+static void run_client(int fd)
+{
+ char *buf = malloc(BULK_LEN);
+
+ if (!buf)
+ die("malloc");
+
+ fill_pattern(buf, BULK_LEN, SEED_C2S_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_S2C_BULK, "server -> client bulk");
+
+ send_msg_more(fd, SEED_C2S_MORE);
+ send_splice(fd, SEED_C2S_SPLICE);
+ send_small_records(fd, SEED_C2S_SMALL);
+
+ /* Wait for the server's verdict before tearing anything down. */
+ read_all(fd, buf, 1);
+ if (buf[0] != 'k')
+ fail("server reported a failure");
+
+ free(buf);
+}
+
+static void run_server(int fd)
+{
+ size_t splice_len = (size_t)SPLICE_FRAG_LEN * SPLICE_FRAGS;
+ char *buf = malloc(BULK_LEN);
+ char more[MORE_FRAGS + 1];
+ char *sbuf;
+ char ok = 'k';
+ int i;
+
+ sbuf = malloc(splice_len);
+ if (!buf || !sbuf)
+ die("malloc");
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_C2S_BULK, "client -> server bulk");
+
+ fill_pattern(buf, BULK_LEN, SEED_S2C_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, more, sizeof(more));
+ check_pattern(more, sizeof(more), SEED_C2S_MORE, "client -> server MSG_MORE");
+
+ read_all(fd, sbuf, splice_len);
+ for (i = 0; i < SPLICE_FRAGS; i++)
+ check_pattern(sbuf + (size_t)i * SPLICE_FRAG_LEN,
+ SPLICE_FRAG_LEN, SEED_C2S_SPLICE +
+ i * SPLICE_FRAG_LEN, "client -> server splice");
+
+ read_all(fd, buf, SMALL_LEN);
+ check_pattern(buf, SMALL_LEN, SEED_C2S_SMALL,
+ "client -> server small records");
+
+ write_all(fd, &ok, 1);
+
+ free(sbuf);
+ free(buf);
+}
+
+static int do_server(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ int lfd, fd, one = 1;
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (lfd < 0)
+ die("socket");
+ if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
+ die("SO_REUSEADDR");
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad bind address %s", ip);
+
+ if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)))
+ die("bind");
+ if (listen(lfd, 1))
+ die("listen");
+
+ fd = accept(lfd, NULL, NULL);
+ if (fd < 0)
+ die("accept");
+ close(lfd);
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "server", "client");
+ wait_for_go(syncdir);
+
+ run_server(fd);
+
+ close(fd);
+ return 0;
+}
+
+static int do_client(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ unsigned int waited = 0;
+ int fd;
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad server address %s", ip);
+
+ for (;;) {
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ die("socket");
+ if (!connect(fd, (struct sockaddr *)&sa, sizeof(sa)))
+ break;
+ close(fd);
+ if (waited >= CONNECT_TIMEOUT_MS)
+ die("connect");
+ msleep(20);
+ waited += 20;
+ }
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "client", "server");
+ wait_for_go(syncdir);
+
+ run_client(fd);
+
+ close(fd);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int port;
+
+ if (argc != 5) {
+ fprintf(stderr,
+ "usage: %s server|client <ip> <port> <syncdir>\n",
+ argv[0]);
+ return 2;
+ }
+
+ role = argv[1];
+ port = atoi(argv[3]);
+
+ alarm(RUN_TIMEOUT_SEC);
+
+ if (!strcmp(role, "server"))
+ return do_server(argv[2], port, argv[4]);
+ if (!strcmp(role, "client"))
+ return do_client(argv[2], port, argv[4]);
+
+ fprintf(stderr, "unknown role %s\n", role);
+ return 2;
+}
|
| 23/3 | 2026/09/02 05:57 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19426 output: 18 thoughts: 1363 Thoughts: **KMSAN Analysis of netdevsim TLS Offload Emulation Patch** Okay, let's break down this patch series. My goal is to determine if it introduces any new risks for uninitialized memory reads or information leaks, and whether we need a dedicated KMSAN fuzzing session. Let's dig in. The patch implements TLS offload emulation within `netdevsim`. Looking at the diff, it seems to involve modifications to several files: `tls.c`, `netdevsim.h`, `netdev.c`, along with updates to `Kconfig` and `Makefile`, and the addition of self tests. First, I examined `tls.c` methodically: 1. `nsim_tls_parse`: This function parses IP/IPv6 headers. It extracts addresses and ports into a `nsim_tls_tuple` structure. Crucially, all fields of `nsim_tls_tuple` are *definitely* initialized if the function returns successfully (returning `true`), which means that any use of the data will have guaranteed initialization. If it returns false, the struct is not used, so safe. 2. `nsim_tls_rx_offloaded`: This uses the initialized `t` variable. It then checks `conn->started` which is read via READ_ONCE. This is safe. 3. `nsim_tls_tx_zero_tags`: Here, the code *explicitly* zeroes a tag-sized region at the end of each record's last fragment, intended for an authentication tag. The comment is very clear, this is done *to prevent* a leak of uninitialized memory. So, this operation actually *prevents* any such risk, it does not create one. 4. `nsim_tls_tx`: This calls `nsim_tls_tx_zero_tags` and increments some atomic counters, all of which are safe. 5. `nsim_tls_rx`: This simply sets `skb->decrypted = 1;` - safe. 6. `nsim_do_tls`: This checks `skb->encapsulation` and calls `nsim_tls_tx` and `nsim_tls_rx`. No concerns. 7. `nsim_tls_dev_add`: This allocates `conn` using `kzalloc_obj` (or `kzalloc`) which *guarantees* zero initialization for the new connection structure. The code then explicitly fills out several fields (`conn->laddr`, `conn->raddr`, etc.), so zero-initialization is ensured. 8. `nsim_tls_dev_del`: Finds and deletes the connection. Uses `kfree_rcu` after. This is safe. 9. `nsim_tls_dbg_read`: This debugfs handler allocates a buffer using `kzalloc`, populates it with formatted strings via `scnprintf`, and then reads from it. The buffer length is precise, and `kzalloc` guarantees initialization. So, no issues here. 10. `nsim_tls_init`: Initializes the TLS connection list head and spinlock, and creates a debugfs file. Safe. 11. `nsim_tls_teardown`: Removes the debugfs file and cleans up the connection list, using `kfree_rcu`. Also safe. Now, let's look at broader allocations. The `netdevsim` struct itself, which holds the TLS context, is allocated in `nsim_create()` using `alloc_netdev_mqs`. This function, crucially, either zero-initializes the private netdev data or uses `kzalloc`. I need to double-check `netdev.c` source if using a grepper or read-file. Based on this analysis, I see no immediate evidence of uninitialized memory reads or information leaks introduced by this patch series. Everything seems to be carefully initialized, zeroed where necessary, and the code explicitly addresses the potential for leaks. Therefore, I don't see any immediate justification for a dedicated KMSAN fuzzing session. We should be good to go. |
| 24/3 | 2026/09/02 05:57 | tool | grepper |
0mArgs:{
"Expression": "nsim_create"
}Results: {
"Output": "drivers/net/netdevsim/dev.c=1380=static int __nsim_dev_port_add(struct nsim_dev *nsim_dev, enum nsim_dev_port_type type,\n--\ndrivers/net/netdevsim/dev.c-1433-\ndrivers/net/netdevsim/dev.c:1434:\tnsim_dev_port-\u003ens = nsim_create(nsim_dev, nsim_dev_port, perm_addr);\ndrivers/net/netdevsim/dev.c-1435-\tif (IS_ERR(nsim_dev_port-\u003ens)) {\n--\ndrivers/net/netdevsim/netdev.c=407=static int nsim_poll(struct napi_struct *napi, int budget)\n--\ndrivers/net/netdevsim/netdev.c-418-\ndrivers/net/netdevsim/netdev.c:419:static int nsim_create_page_pool(struct page_pool **p, struct napi_struct *napi)\ndrivers/net/netdevsim/netdev.c-420-{\n--\ndrivers/net/netdevsim/netdev.c=440=static int nsim_init_napi(struct netdevsim *ns)\n--\ndrivers/net/netdevsim/netdev.c-454-\ndrivers/net/netdevsim/netdev.c:455:\t\terr = nsim_create_page_pool(\u0026rq-\u003epage_pool, \u0026rq-\u003enapi);\ndrivers/net/netdevsim/netdev.c-456-\t\tif (err)\n--\ndrivers/net/netdevsim/netdev.c=753=nsim_queue_mem_alloc(struct net_device *dev,\n--\ndrivers/net/netdevsim/netdev.c-766-\t\t\treturn -ENETDOWN;\ndrivers/net/netdevsim/netdev.c:767:\t\treturn nsim_create_page_pool(\u0026qmem-\u003epp, \u0026ns-\u003erq[idx]-\u003enapi);\ndrivers/net/netdevsim/netdev.c-768-\t}\n--\ndrivers/net/netdevsim/netdev.c-773-\ndrivers/net/netdevsim/netdev.c:774:\terr = nsim_create_page_pool(\u0026qmem-\u003erq-\u003epage_pool, \u0026qmem-\u003erq-\u003enapi);\ndrivers/net/netdevsim/netdev.c-775-\tif (err)\n--\ndrivers/net/netdevsim/netdev.c=1126=static void nsim_exit_netdevsim(struct netdevsim *ns)\n--\ndrivers/net/netdevsim/netdev.c-1131-\ndrivers/net/netdevsim/netdev.c:1132:struct netdevsim *nsim_create(struct nsim_dev *nsim_dev,\ndrivers/net/netdevsim/netdev.c-1133-\t\t\t struct nsim_dev_port *nsim_dev_port,\n--\ndrivers/net/netdevsim/netdevsim.h=121=struct netdevsim {\n--\ndrivers/net/netdevsim/netdevsim.h-180-\ndrivers/net/netdevsim/netdevsim.h:181:struct netdevsim *nsim_create(struct nsim_dev *nsim_dev,\ndrivers/net/netdevsim/netdevsim.h-182-\t\t\t struct nsim_dev_port *nsim_dev_port,\n"
} |
| 25/3 | 2026/09/02 05:57 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22583 output: 41 thoughts: 25 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into `nsim_create` in `netdev.c`** Okay, time to dig into the `netdevsim` driver. I need to refresh my memory on how simulated network devices are created. Specifically, I'm going to jump into `drivers/net/netdevsim/netdev.c` and take a look at the `nsim_create` function. I'm assuming that's where the core device instantiation logic lives, but it’s always good to confirm. I'll probably be focusing on how the device structure is initialized, any memory allocations, and how it's linked into the kernel's network stack. I’m especially interested in whether there are any potential race conditions or locking issues within that function, given the concurrent nature of network device creation and deletion. Also, it might be worth verifying how it handles the device's associated parameters and any resource constraints. Let's see if the code has evolved since my last detailed review. I also wonder how the simulated hardware characteristics are defined during the device's setup. |
| 26/3 | 2026/09/02 05:57 | tool | read-file |
0mArgs:{
"File": "drivers/net/netdevsim/netdev.c",
"FirstLine": 1132,
"LineCount": 40
}Results: {
"Contents": "1132:\tstruct netdevsim *nsim_create(struct nsim_dev *nsim_dev,\n1133:\t\t\t\t struct nsim_dev_port *nsim_dev_port,\n1134:\t\t\t\t u8 perm_addr[ETH_ALEN])\n1135:\t{\n1136:\t\tstruct net_device *dev;\n1137:\t\tstruct netdevsim *ns;\n1138:\t\tint err;\n1139:\t\n1140:\t\tdev = alloc_netdev_mq(sizeof(*ns), \"eth%d\", NET_NAME_UNKNOWN, nsim_setup,\n1141:\t\t\t\t nsim_dev-\u003ensim_bus_dev-\u003enum_queues);\n1142:\t\tif (!dev)\n1143:\t\t\treturn ERR_PTR(-ENOMEM);\n1144:\t\n1145:\t\tif (perm_addr)\n1146:\t\t\tmemcpy(dev-\u003eperm_addr, perm_addr, ETH_ALEN);\n1147:\t\n1148:\t\tdev_net_set(dev, nsim_dev_net(nsim_dev));\n1149:\t\tns = netdev_priv(dev);\n1150:\t\tns-\u003enetdev = dev;\n1151:\t\tns-\u003ensim_dev = nsim_dev;\n1152:\t\tns-\u003ensim_dev_port = nsim_dev_port;\n1153:\t\tns-\u003ensim_bus_dev = nsim_dev-\u003ensim_bus_dev;\n1154:\t\tSET_NETDEV_DEV(dev, \u0026ns-\u003ensim_bus_dev-\u003edev);\n1155:\t\tSET_NETDEV_DEVLINK_PORT(dev, \u0026nsim_dev_port-\u003edevlink_port);\n1156:\t\tnsim_ethtool_init(ns);\n1157:\t\tif (nsim_dev_port_is_pf(nsim_dev_port))\n1158:\t\t\terr = nsim_init_netdevsim(ns);\n1159:\t\telse\n1160:\t\t\terr = nsim_init_netdevsim_vf(ns);\n1161:\t\tif (err)\n1162:\t\t\tgoto err_free_netdev;\n1163:\t\n1164:\t\tns-\u003epp_dfs = debugfs_create_file(\"pp_hold\", 0600, nsim_dev_port-\u003eddir,\n1165:\t\t\t\t\t\t ns, \u0026nsim_pp_hold_fops);\n1166:\t\tns-\u003eqr_dfs = debugfs_create_file(\"queue_reset\", 0200,\n1167:\t\t\t\t\t\t nsim_dev_port-\u003eddir, ns,\n1168:\t\t\t\t\t\t \u0026nsim_qreset_fops);\n1169:\t\tns-\u003evlan_dfs = debugfs_create_file(\"vlan\", 0400, nsim_dev_port-\u003eddir,\n1170:\t\t\t\t\t\t ns, \u0026nsim_vlan_fops);\n1171:\t\treturn ns;\n"
} |
| 27/3 | 2026/09/02 05:57 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23676 output: 331 thoughts: 603 Thoughts: **My Assessment: KMSAN Applicability - Negative** Okay, let's break this down. From what I've just reviewed, this looks pretty solid from a KMSAN perspective. First, I see the core allocation strategy here is consistently using `kzalloc` or `kzalloc_obj`. That's the key right there – guaranteed zero-initialization. This applies to both the `netdev` itself and its private data, as well as the connection structures (`conn`) and the debug read buffers. We're explicitly initializing list heads and spinlocks too. Now, running through the KMSAN criteria, there's no way this exposes uninitialized data. We're not sending uninitialized kernel data to userspace, at least not that I see. The debugfs output uses `scnprintf` *after* allocating the buffer with `kzalloc`, meaning the memory is already zeroed. There's no conditional logic branching on uninitialized fields. All the structures we're working with get proper zero-initialization. As for complex data structures, again, we're covered with `kzalloc`. Everything's handled, which means no reads later on in reachable code paths from uninitialized fields. And finally, that `nsim_tls_tx_zero_tags()` function is specifically addressing a potential memory leak scenario. It's explicitly zeroing out the gap at the end of the SKB frags. That's good practice, and it further reinforces that we're being careful about potential uninitialized data. So, *none* of the KMSAN criteria are triggered here. **NeedsKMSAN = False**. Now, let's consider why KASAN *might* be relevant, versus KMSAN. This is an emulated kTLS offload, so there's potential for all sorts of concurrency and data structure errors which is what KASAN is built to catch. We're working with list manipulations, SKB parsing/manipulation, and possibly some concurrent access. However, all the core kernel allocations have been handled such that all KMSAN issues are resolved. Things like lockdep, KASAN, and standard kernel debugging tools will be sufficient for memory safety here. I'm comfortable making this determination. Let's get the `set-results` tool going. |
| 28/3 | 2026/09/02 05:57 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series implements emulated kTLS offload support for netdevsim. Analysis of memory initialization and handling across the patch shows:\n\n1. Memory allocation and initialization: All dynamic structures (such as `struct nsim_tls_conn` and debugfs read buffers) are allocated via `kzalloc_obj` or `kzalloc`, ensuring all struct fields are zero-initialized. List heads and spinlocks are explicitly initialized in `nsim_tls_init`.\n2. Stack variables: `struct nsim_tls_tuple` is fully populated by `nsim_tls_parse()` before any fields are read or compared in `nsim_tls_rx_offloaded()`.\n3. Userspace data transfer: The debugfs output in `nsim_tls_dbg_read()` uses `scnprintf()` into a zeroed buffer and copies exactly `p - buf` bytes via `simple_read_from_buffer()`.\n4. Packet tag handling: `nsim_tls_tx_zero_tags()` explicitly `memset`s the trailing record tag gap to zero to prevent sending uninitialized bytes across the simulated wire.\n\nPotential memory safety bugs in this driver extension (e.g., skb bounds, RCU lifetime management, concurrency, or list corruption) are detectable by KASAN, LOCKDEP, and standard sanitizers. There is no risk of uninitialized memory usage or info-leaks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series implements emulated kTLS offload support for netdevsim. Analysis of memory initialization and handling across the patch shows:\n\n1. Memory allocation and initialization: All dynamic structures (such as `struct nsim_tls_conn` and debugfs read buffers) are allocated via `kzalloc_obj` or `kzalloc`, ensuring all struct fields are zero-initialized. List heads and spinlocks are explicitly initialized in `nsim_tls_init`.\n2. Stack variables: `struct nsim_tls_tuple` is fully populated by `nsim_tls_parse()` before any fields are read or compared in `nsim_tls_rx_offloaded()`.\n3. Userspace data transfer: The debugfs output in `nsim_tls_dbg_read()` uses `scnprintf()` into a zeroed buffer and copies exactly `p - buf` bytes via `simple_read_from_buffer()`.\n4. Packet tag handling: `nsim_tls_tx_zero_tags()` explicitly `memset`s the trailing record tag gap to zero to prevent sending uninitialized bytes across the simulated wire.\n\nPotential memory safety bugs in this driver extension (e.g., skb bounds, RCU lifetime management, concurrency, or list corruption) are detectable by KASAN, LOCKDEP, and standard sanitizers. There is no risk of uninitialized memory usage or info-leaks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|