AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/20 22:04 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "NFSD",
    "TLS"
  ],
  "FocusSymbols": [
    "svc_tcp_recv_actor",
    "svc_tcp_rectype_actor",
    "svc_tcp_recvfrom",
    "tls_sw_read_sock_rectype",
    "__tls_sw_read_sock"
  ],
  "Reasoning": "The patch introduces a new proto_ops callback `read_sock_rectype` to allow reading non-data TLS records (like alerts and handshake messages) directly from the socket. It implements this in the TLS software path (`__tls_sw_read_sock`, `tls_sw_read_sock_rectype`) and uses it in the SUNRPC server TCP socket code (`svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `svc_tcp_recvfrom`). This is a functional change in core networking and RPC code that should be fuzzed to ensure no regressions or vulnerabilities are introduced in the TLS record parsing and RPC message assembly logic.",
  "WorthFuzzing": true
}

1/1 2026/07/20 22:04 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 2c44b0239b317ff488181d7925c4ecb05ad186b2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 20 22:04:50 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/net.h b/include/linux/net.h\nindex 277188a40c72e3..7a19a743a61776 100644\n--- a/include/linux/net.h\n+++ b/include/linux/net.h\n@@ -198,6 +198,13 @@ struct sk_buff;\n struct proto_accept_arg;\n typedef int (*sk_read_actor_t)(read_descriptor_t *, struct sk_buff *,\n \t\t\t       unsigned int, size_t);\n+/* rectype carries the transport record type, for example a\n+ * TLS_RECORD_TYPE_* value.\n+ */\n+typedef int (*sk_read_rectype_actor_t)(read_descriptor_t *,\n+\t\t\t\t       struct sk_buff *,\n+\t\t\t\t       unsigned int, size_t,\n+\t\t\t\t       u8 rectype);\n typedef int (*skb_read_actor_t)(struct sock *, struct sk_buff *);\n \n \n@@ -264,6 +271,27 @@ struct proto_ops {\n \t */\n \tint\t\t(*read_sock)(struct sock *sk, read_descriptor_t *desc,\n \t\t\t\t     sk_read_actor_t recv_actor);\n+\t/*\n+\t * read_sock_rectype splits delivery across two callbacks:\n+\t * recv_actor for data records, per the sk_read_actor_t\n+\t * convention, and rectype_actor for all other records,\n+\t * with rectype identifying each. A NULL rectype_actor\n+\t * leaves non-data records pending. rectype_actor returns 0\n+\t * to consume a record or negative to leave it pending for\n+\t * redelivery and stop delivery; the negative return is a\n+\t * backpressure signal, not a fatal error. Both callbacks\n+\t * report errors and early stop the way recv_actor does:\n+\t * by setting desc-\u003ecount to 0 and recording the reason in\n+\t * desc-\u003eerror, per the read_descriptor_t convention and\n+\t * independent of the return value. The return value reports\n+\t * only data bytes consumed by recv_actor; the caller\n+\t * detects an error or early stop via desc-\u003ecount and\n+\t * desc-\u003eerror.\n+\t */\n+\tint\t\t(*read_sock_rectype)(struct sock *sk,\n+\t\t\t\t\t     read_descriptor_t *desc,\n+\t\t\t\t\t     sk_read_actor_t recv_actor,\n+\t\t\t\t\t     sk_read_rectype_actor_t rectype_actor);\n \t/* This is different from read_sock(), it reads an entire skb at a time. */\n \tint\t\t(*read_skb)(struct sock *sk, skb_read_actor_t recv_actor);\n \tint\t\t(*sendmsg_locked)(struct sock *sk, struct msghdr *msg,\ndiff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c\nindex 50e5e7f5b762de..9b9e0da9e73cd6 100644\n--- a/net/sunrpc/svcsock.c\n+++ b/net/sunrpc/svcsock.c\n@@ -8,15 +8,6 @@\n  * evenly when servicing a single client. May need to modify the\n  * svc_xprt_enqueue procedure...\n  *\n- * TCP support is largely untested and may be a little slow. The problem\n- * is that we currently do two separate recvfrom's, one for the 4-byte\n- * record length, and the second for the actual record. This could possibly\n- * be improved by always reading a minimum size of around 100 bytes and\n- * tucking any superfluous bytes away in a temporary store. Still, that\n- * leaves write requests out in the rain. An alternative may be to peek at\n- * the first skb in the queue, and if it matches the next TCP sequence\n- * number, to extract the record marker. Yuck.\n- *\n  * Copyright (C) 1995, 1996 Olaf Kirch \u003cokir@monad.swb.de\u003e\n  */\n \n@@ -238,138 +229,6 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining)\n \treturn len;\n }\n \n-static int\n-svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,\n-\t\t\t  struct cmsghdr *cmsg, int ret)\n-{\n-\tu8 content_type = tls_get_record_type(sock-\u003esk, cmsg);\n-\tu8 level, description;\n-\n-\tswitch (content_type) {\n-\tcase 0:\n-\t\tbreak;\n-\tcase TLS_RECORD_TYPE_DATA:\n-\t\t/* TLS sets EOR at the end of each application data\n-\t\t * record, even though there might be more frames\n-\t\t * waiting to be decrypted.\n-\t\t */\n-\t\tmsg-\u003emsg_flags \u0026= ~MSG_EOR;\n-\t\tbreak;\n-\tcase TLS_RECORD_TYPE_ALERT:\n-\t\ttls_alert_recv(sock-\u003esk, msg, \u0026level, \u0026description);\n-\t\tret = (level == TLS_ALERT_LEVEL_FATAL) ?\n-\t\t\t-ENOTCONN : -EAGAIN;\n-\t\tbreak;\n-\tdefault:\n-\t\t/* discard this record type */\n-\t\tret = -EAGAIN;\n-\t}\n-\treturn ret;\n-}\n-\n-static int\n-svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)\n-{\n-\tunion {\n-\t\tstruct cmsghdr\tcmsg;\n-\t\tu8\t\tbuf[CMSG_SPACE(sizeof(u8))];\n-\t} u;\n-\tu8 alert[2];\n-\tstruct kvec alert_kvec = {\n-\t\t.iov_base = alert,\n-\t\t.iov_len = sizeof(alert),\n-\t};\n-\tstruct msghdr msg = {\n-\t\t.msg_flags = *msg_flags,\n-\t\t.msg_control = \u0026u,\n-\t\t.msg_controllen = sizeof(u),\n-\t};\n-\tint ret;\n-\n-\tiov_iter_kvec(\u0026msg.msg_iter, ITER_DEST, \u0026alert_kvec, 1,\n-\t\t      alert_kvec.iov_len);\n-\tret = sock_recvmsg(sock, \u0026msg, MSG_DONTWAIT);\n-\tif (ret \u003e 0 \u0026\u0026\n-\t    tls_get_record_type(sock-\u003esk, \u0026u.cmsg) == TLS_RECORD_TYPE_ALERT) {\n-\t\tiov_iter_revert(\u0026msg.msg_iter, ret);\n-\t\tret = svc_tcp_sock_process_cmsg(sock, \u0026msg, \u0026u.cmsg, -EAGAIN);\n-\t}\n-\treturn ret;\n-}\n-\n-static int\n-svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)\n-{\n-\tint ret;\n-\tstruct socket *sock = svsk-\u003esk_sock;\n-\n-\tret = sock_recvmsg(sock, msg, MSG_DONTWAIT);\n-\tif (msg-\u003emsg_flags \u0026 MSG_CTRUNC) {\n-\t\tmsg-\u003emsg_flags \u0026= ~(MSG_CTRUNC | MSG_EOR);\n-\t\tif (ret == 0 || ret == -EIO)\n-\t\t\tret = svc_tcp_sock_recv_cmsg(sock, \u0026msg-\u003emsg_flags);\n-\t}\n-\treturn ret;\n-}\n-\n-#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE\n-static void svc_flush_bvec(const struct bio_vec *bvec, size_t size, size_t seek)\n-{\n-\tstruct bvec_iter bi = {\n-\t\t.bi_size\t= size + seek,\n-\t};\n-\tstruct bio_vec bv;\n-\n-\tbvec_iter_advance(bvec, \u0026bi, seek \u0026 PAGE_MASK);\n-\tfor_each_bvec(bv, bvec, bi, bi)\n-\t\tflush_dcache_page(bv.bv_page);\n-}\n-#else\n-static inline void svc_flush_bvec(const struct bio_vec *bvec, size_t size,\n-\t\t\t\t  size_t seek)\n-{\n-}\n-#endif\n-\n-/*\n- * Read from @rqstp's transport socket. The incoming message fills whole\n- * pages in @rqstp's rq_pages array until the last page of the message\n- * has been received into a partial page.\n- */\n-static ssize_t svc_tcp_read_msg(struct svc_rqst *rqstp, size_t buflen,\n-\t\t\t\tsize_t seek)\n-{\n-\tstruct svc_sock *svsk =\n-\t\tcontainer_of(rqstp-\u003erq_xprt, struct svc_sock, sk_xprt);\n-\tstruct bio_vec *bvec = rqstp-\u003erq_bvec;\n-\tstruct msghdr msg = { NULL };\n-\tunsigned int i;\n-\tssize_t len;\n-\tsize_t t;\n-\n-\tclear_bit(XPT_DATA, \u0026svsk-\u003esk_xprt.xpt_flags);\n-\n-\tfor (i = 0, t = 0; t \u003c buflen; i++, t += PAGE_SIZE)\n-\t\tbvec_set_page(\u0026bvec[i], rqstp-\u003erq_pages[i], PAGE_SIZE, 0);\n-\n-\tiov_iter_bvec(\u0026msg.msg_iter, ITER_DEST, bvec, i, buflen);\n-\tif (seek) {\n-\t\tiov_iter_advance(\u0026msg.msg_iter, seek);\n-\t\tbuflen -= seek;\n-\t}\n-\tlen = svc_tcp_sock_recvmsg(svsk, \u0026msg);\n-\tif (len \u003e 0)\n-\t\tsvc_flush_bvec(bvec, len, seek);\n-\n-\t/* If we read a full record, then assume there may be more\n-\t * data to read (stream based sockets only!)\n-\t */\n-\tif (len == buflen)\n-\t\tset_bit(XPT_DATA, \u0026svsk-\u003esk_xprt.xpt_flags);\n-\n-\treturn len;\n-}\n-\n /*\n  * Set socket snd and rcv buffer lengths\n  */\n@@ -1048,50 +907,6 @@ static void svc_tcp_clear_pages(struct svc_sock *svsk)\n \tsvsk-\u003esk_datalen = 0;\n }\n \n-/*\n- * Receive fragment record header into sk_marker.\n- */\n-static ssize_t svc_tcp_read_marker(struct svc_sock *svsk,\n-\t\t\t\t   struct svc_rqst *rqstp)\n-{\n-\tssize_t want, len;\n-\n-\t/* If we haven't gotten the record length yet,\n-\t * get the next four bytes.\n-\t */\n-\tif (svsk-\u003esk_tcplen \u003c sizeof(rpc_fraghdr)) {\n-\t\tstruct msghdr\tmsg = { NULL };\n-\t\tstruct kvec\tiov;\n-\n-\t\twant = sizeof(rpc_fraghdr) - svsk-\u003esk_tcplen;\n-\t\tiov.iov_base = ((char *)\u0026svsk-\u003esk_marker) + svsk-\u003esk_tcplen;\n-\t\tiov.iov_len  = want;\n-\t\tiov_iter_kvec(\u0026msg.msg_iter, ITER_DEST, \u0026iov, 1, want);\n-\t\tlen = svc_tcp_sock_recvmsg(svsk, \u0026msg);\n-\t\tif (len \u003c 0)\n-\t\t\treturn len;\n-\t\tsvsk-\u003esk_tcplen += len;\n-\t\tif (len \u003c want) {\n-\t\t\t/* call again to read the remaining bytes */\n-\t\t\tgoto err_short;\n-\t\t}\n-\t\ttrace_svcsock_marker(\u0026svsk-\u003esk_xprt, svsk-\u003esk_marker);\n-\t\tif (svc_sock_reclen(svsk) + svsk-\u003esk_datalen \u003e\n-\t\t    svsk-\u003esk_xprt.xpt_server-\u003esv_max_mesg)\n-\t\t\tgoto err_too_large;\n-\t}\n-\treturn svc_sock_reclen(svsk);\n-\n-err_too_large:\n-\tnet_notice_ratelimited(\"svc: %s oversized RPC fragment (%u octets) from %pISpc\\n\",\n-\t\t\t       svsk-\u003esk_xprt.xpt_server-\u003esv_name,\n-\t\t\t       svc_sock_reclen(svsk),\n-\t\t\t       (struct sockaddr *)\u0026svsk-\u003esk_xprt.xpt_remote);\n-\tsvc_xprt_deferred_close(\u0026svsk-\u003esk_xprt);\n-err_short:\n-\treturn -EAGAIN;\n-}\n-\n static int receive_cb_reply(struct svc_sock *svsk, struct svc_rqst *rqstp)\n {\n \tstruct rpc_xprt *bc_xprt = svsk-\u003esk_xprt.xpt_bc_xprt;\n@@ -1134,16 +949,144 @@ static void svc_tcp_fragment_received(struct svc_sock *svsk)\n \tsvsk-\u003esk_marker = xdr_zero;\n }\n \n+/*\n+ * read_sock data actor: receives application data from the\n+ * transport socket, parsing the RPC record stream (fragment\n+ * headers and message bodies) and assembling complete RPC\n+ * messages into rqstp-\u003erq_pages.\n+ */\n+static int svc_tcp_recv_actor(read_descriptor_t *desc,\n+\t\t\t      struct sk_buff *skb,\n+\t\t\t      unsigned int offset, size_t len)\n+{\n+\tstruct svc_rqst *rqstp = desc-\u003earg.data;\n+\tstruct svc_sock *svsk =\n+\t\tcontainer_of(rqstp-\u003erq_xprt, struct svc_sock, sk_xprt);\n+\tsize_t reclen, received, want, take, done, n;\n+\tsize_t consumed = 0;\n+\n+\tif (!desc-\u003ecount)\n+\t\treturn 0;\n+\n+\tif (svsk-\u003esk_tcplen \u003c sizeof(rpc_fraghdr)) {\n+\t\twant = sizeof(rpc_fraghdr) - svsk-\u003esk_tcplen;\n+\t\tn = min(want, len);\n+\n+\t\tif (skb_copy_bits(skb, offset,\n+\t\t\t\t  (char *)\u0026svsk-\u003esk_marker +\n+\t\t\t\t\tsvsk-\u003esk_tcplen, n))\n+\t\t\tgoto fault;\n+\t\tsvsk-\u003esk_tcplen += n;\n+\t\toffset += n;\n+\t\tlen -= n;\n+\t\tconsumed += n;\n+\n+\t\tif (svsk-\u003esk_tcplen \u003c sizeof(rpc_fraghdr))\n+\t\t\treturn consumed;\n+\n+\t\ttrace_svcsock_marker(\u0026svsk-\u003esk_xprt, svsk-\u003esk_marker);\n+\t\tif (svc_sock_reclen(svsk) + svsk-\u003esk_datalen \u003e\n+\t\t    svsk-\u003esk_xprt.xpt_server-\u003esv_max_mesg) {\n+\t\t\tnet_notice_ratelimited(\"svc: %s oversized RPC fragment (%u octets) from %pISpc\\n\",\n+\t\t\t\t\t       svsk-\u003esk_xprt.xpt_server-\u003esv_name,\n+\t\t\t\t\t       svc_sock_reclen(svsk),\n+\t\t\t\t\t       (struct sockaddr *)\u0026svsk-\u003esk_xprt.xpt_remote);\n+\t\t\tdesc-\u003eerror = -EMSGSIZE;\n+\t\t\tdesc-\u003ecount = 0;\n+\t\t\treturn consumed;\n+\t\t}\n+\t}\n+\n+\treclen = svc_sock_reclen(svsk);\n+\treceived = svsk-\u003esk_tcplen - sizeof(rpc_fraghdr);\n+\twant = reclen - received;\n+\ttake = min(want, len);\n+\tdone = 0;\n+\n+\twhile (done \u003c take) {\n+\t\tunsigned int pg = svsk-\u003esk_datalen \u003e\u003e PAGE_SHIFT;\n+\t\tunsigned int pg_off = svsk-\u003esk_datalen \u0026 (PAGE_SIZE - 1);\n+\t\tsize_t chunk = min(take - done,\n+\t\t\t\t   PAGE_SIZE - (size_t)pg_off);\n+\n+\t\tif (skb_copy_bits(skb, offset,\n+\t\t\t\t  page_address(rqstp-\u003erq_pages[pg]) + pg_off,\n+\t\t\t\t  chunk))\n+\t\t\tgoto fault;\n+\t\tflush_dcache_page(rqstp-\u003erq_pages[pg]);\n+\t\toffset += chunk;\n+\t\tdone += chunk;\n+\t\tsvsk-\u003esk_datalen += chunk;\n+\t}\n+\tsvsk-\u003esk_tcplen += take;\n+\tconsumed += take;\n+\n+\tif (svsk-\u003esk_tcplen - sizeof(rpc_fraghdr) \u003e= reclen) {\n+\t\tif (svc_sock_final_rec(svsk))\n+\t\t\tdesc-\u003ecount = 0;\n+\t\telse\n+\t\t\tsvc_tcp_fragment_received(svsk);\n+\t}\n+\n+\treturn consumed;\n+\n+fault:\n+\tdesc-\u003eerror = -EFAULT;\n+\tdesc-\u003ecount = 0;\n+\treturn consumed;\n+}\n+\n+/*\n+ * read_sock_rectype non-data record actor: receives non-data TLS\n+ * records (alerts, handshake messages) and translates them into\n+ * transport-level actions.\n+ *\n+ * Returns 0 to consume the record and allow the TLS layer to\n+ * continue delivering subsequent records. A negative return\n+ * causes the TLS layer to requeue the skb on its rx_list,\n+ * blocking all further record delivery on this connection.\n+ */\n+static int svc_tcp_rectype_actor(read_descriptor_t *desc,\n+\t\t\t\t struct sk_buff *skb,\n+\t\t\t\t unsigned int offset, size_t len,\n+\t\t\t\t u8 rectype)\n+{\n+\tstruct svc_rqst *rqstp = desc-\u003earg.data;\n+\tstruct svc_sock *svsk =\n+\t\tcontainer_of(rqstp-\u003erq_xprt, struct svc_sock, sk_xprt);\n+\n+\tswitch (rectype) {\n+\tcase TLS_RECORD_TYPE_ALERT: {\n+\t\tu8 alert[2] = {}, level, description;\n+\t\tstruct kvec kvec = {\n+\t\t\t.iov_base = alert,\n+\t\t\t.iov_len = sizeof(alert),\n+\t\t};\n+\t\tstruct msghdr msg = {};\n+\n+\t\tif (skb_copy_bits(skb, offset, alert, min(len, sizeof(alert))))\n+\t\t\tbreak;\n+\t\tiov_iter_kvec(\u0026msg.msg_iter, ITER_DEST, \u0026kvec, 1, sizeof(alert));\n+\t\ttls_alert_recv(svsk-\u003esk_sk, \u0026msg, \u0026level, \u0026description);\n+\t\tif (level == TLS_ALERT_LEVEL_FATAL) {\n+\t\t\tsvc_xprt_deferred_close(\u0026svsk-\u003esk_xprt);\n+\t\t\tdesc-\u003eerror = -ENOTCONN;\n+\t\t\tdesc-\u003ecount = 0;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn 0;\n+}\n+\n /**\n  * svc_tcp_recvfrom - Receive data from a TCP socket\n  * @rqstp: request structure into which to receive an RPC Call\n  *\n  * Called in a loop when XPT_DATA has been set.\n  *\n- * Read the 4-byte stream record marker, then use the record length\n- * in that marker to set up exactly the resources needed to receive\n- * the next RPC message into @rqstp.\n- *\n  * Returns:\n  *   On success, the number of bytes in a received RPC Call, or\n  *   %0 if a complete RPC Call message was not ready to return\n@@ -1157,26 +1100,40 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n \tstruct svc_sock\t*svsk =\n \t\tcontainer_of(rqstp-\u003erq_xprt, struct svc_sock, sk_xprt);\n \tstruct svc_serv\t*serv = svsk-\u003esk_xprt.xpt_server;\n-\tsize_t want, base;\n+\tstruct sock\t*sk = svsk-\u003esk_sk;\n+\tread_descriptor_t desc = {\n+\t\t.arg.data = rqstp,\n+\t};\n \tssize_t len;\n \t__be32 *p;\n \t__be32 calldir;\n \n \tclear_bit(XPT_DATA, \u0026svsk-\u003esk_xprt.xpt_flags);\n-\tlen = svc_tcp_read_marker(svsk, rqstp);\n-\tif (len \u003c 0)\n-\t\tgoto error;\n \n-\tbase = svc_tcp_restore_pages(svsk, rqstp);\n-\twant = len - (svsk-\u003esk_tcplen - sizeof(rpc_fraghdr));\n-\tlen = svc_tcp_read_msg(rqstp, base + want, base);\n-\tif (len \u003e= 0) {\n-\t\ttrace_svcsock_tcp_recv(\u0026svsk-\u003esk_xprt, len);\n-\t\tsvsk-\u003esk_tcplen += len;\n-\t\tsvsk-\u003esk_datalen += len;\n+\tsvc_tcp_restore_pages(svsk, rqstp);\n+\trqstp-\u003erq_arg.head[0].iov_base = page_address(rqstp-\u003erq_pages[0]);\n+\n+\tdesc.count = serv-\u003esv_max_mesg;\n+\tlock_sock(sk);\n+\tif (svsk-\u003esk_sock-\u003eops-\u003eread_sock_rectype)\n+\t\tlen = svsk-\u003esk_sock-\u003eops-\u003eread_sock_rectype(sk, \u0026desc,\n+\t\t\t\t\t\t\t    svc_tcp_recv_actor,\n+\t\t\t\t\t\t\t    svc_tcp_rectype_actor);\n+\telse\n+\t\tlen = svsk-\u003esk_sock-\u003eops-\u003eread_sock(sk, \u0026desc,\n+\t\t\t\t\t\t     svc_tcp_recv_actor);\n+\trelease_sock(sk);\n+\n+\tif (desc.error \u003c 0) {\n+\t\tlen = desc.error;\n+\t\tgoto err_discard;\n \t}\n-\tif (len != want || !svc_sock_final_rec(svsk))\n+\tif (desc.count != 0) {\n+\t\tif (len \u003e 0)\n+\t\t\tset_bit(XPT_DATA, \u0026svsk-\u003esk_xprt.xpt_flags);\n \t\tgoto err_incomplete;\n+\t}\n+\n \tif (svsk-\u003esk_datalen \u003c 8)\n \t\tgoto err_nuts;\n \n@@ -1185,8 +1142,10 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n \tif (rqstp-\u003erq_arg.len \u003c= rqstp-\u003erq_arg.head[0].iov_len) {\n \t\trqstp-\u003erq_arg.head[0].iov_len = rqstp-\u003erq_arg.len;\n \t\trqstp-\u003erq_arg.page_len = 0;\n-\t} else\n-\t\trqstp-\u003erq_arg.page_len = rqstp-\u003erq_arg.len - rqstp-\u003erq_arg.head[0].iov_len;\n+\t} else {\n+\t\trqstp-\u003erq_arg.page_len = rqstp-\u003erq_arg.len -\n+\t\t\trqstp-\u003erq_arg.head[0].iov_len;\n+\t}\n \n \trqstp-\u003erq_xprt_ctxt   = NULL;\n \trqstp-\u003erq_prot\t      = IPPROTO_TCP;\n@@ -1207,11 +1166,13 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n \tif (len \u003c 0)\n \t\tgoto error;\n \n+\ttrace_svcsock_tcp_recv(\u0026svsk-\u003esk_xprt, rqstp-\u003erq_arg.len);\n \tsvc_xprt_copy_addrs(rqstp, \u0026svsk-\u003esk_xprt);\n \tif (serv-\u003esv_stats)\n \t\tserv-\u003esv_stats-\u003enettcpcnt++;\n \n \tsvc_sock_secure_port(rqstp);\n+\tset_bit(XPT_DATA, \u0026svsk-\u003esk_xprt.xpt_flags);\n \tsvc_xprt_received(rqstp-\u003erq_xprt);\n \treturn rqstp-\u003erq_arg.len;\n \n@@ -1219,9 +1180,7 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n \tsvc_tcp_save_pages(svsk, rqstp);\n \tif (len \u003c 0 \u0026\u0026 len != -EAGAIN)\n \t\tgoto err_delete;\n-\tif (len == want)\n-\t\tsvc_tcp_fragment_received(svsk);\n-\telse\n+\tif (svsk-\u003esk_tcplen \u003e= sizeof(rpc_fraghdr))\n \t\ttrace_svcsock_tcp_recv_short(\u0026svsk-\u003esk_xprt,\n \t\t\t\tsvc_sock_reclen(svsk),\n \t\t\t\tsvsk-\u003esk_tcplen - sizeof(rpc_fraghdr));\n@@ -1233,12 +1192,20 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n \tgoto err_noclose;\n err_nuts:\n \tsvsk-\u003esk_datalen = 0;\n+\tgoto err_delete;\n+err_discard:\n+\t/*\n+\t * Clear sk_datalen so the teardown-time\n+\t * svc_tcp_clear_pages() does not walk the emptied\n+\t * svsk-\u003esk_pages[].\n+\t */\n+\tsvsk-\u003esk_datalen = 0;\n err_delete:\n \ttrace_svcsock_tcp_recv_err(\u0026svsk-\u003esk_xprt, len);\n \tsvc_xprt_deferred_close(\u0026svsk-\u003esk_xprt);\n err_noclose:\n \tsvc_xprt_received(rqstp-\u003erq_xprt);\n-\treturn 0;\t/* record not complete */\n+\treturn 0;\n }\n \n /*\ndiff --git a/net/tls/tls.h b/net/tls/tls.h\nindex 60a37bdaaa2506..c21d2a985e1373 100644\n--- a/net/tls/tls.h\n+++ b/net/tls/tls.h\n@@ -168,6 +168,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,\n \t\t\t   size_t len, unsigned int flags);\n int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n \t\t     sk_read_actor_t read_actor);\n+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,\n+\t\t\t     sk_read_actor_t read_actor,\n+\t\t\t     sk_read_rectype_actor_t rectype_actor);\n \n int tls_device_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);\n void tls_device_splice_eof(struct socket *sock);\ndiff --git a/net/tls/tls_main.c b/net/tls/tls_main.c\nindex 8c588cdab733da..4963e0caf6d53a 100644\n--- a/net/tls/tls_main.c\n+++ b/net/tls/tls_main.c\n@@ -949,12 +949,17 @@ static void build_proto_ops(struct proto_ops ops[TLS_NUM_CONFIG][TLS_NUM_CONFIG]\n \tops[TLS_BASE][TLS_SW  ].splice_read\t= tls_sw_splice_read;\n \tops[TLS_BASE][TLS_SW  ].poll\t\t= tls_sk_poll;\n \tops[TLS_BASE][TLS_SW  ].read_sock\t= tls_sw_read_sock;\n+\tops[TLS_BASE][TLS_SW  ].read_sock_rectype\t= tls_sw_read_sock_rectype;\n \n \tops[TLS_SW  ][TLS_SW  ] = ops[TLS_SW  ][TLS_BASE];\n \tops[TLS_SW  ][TLS_SW  ].splice_read\t= tls_sw_splice_read;\n \tops[TLS_SW  ][TLS_SW  ].poll\t\t= tls_sk_poll;\n \tops[TLS_SW  ][TLS_SW  ].read_sock\t= tls_sw_read_sock;\n+\tops[TLS_SW  ][TLS_SW  ].read_sock_rectype\t= tls_sw_read_sock_rectype;\n \n+\t/* TLS_HW (device offload) RX entries inherit\n+\t * read_sock{,_rectype} from SW via the struct copies below.\n+\t */\n #ifdef CONFIG_TLS_DEVICE\n \tops[TLS_HW  ][TLS_BASE] = ops[TLS_BASE][TLS_BASE];\n \ndiff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c\nindex d4afc90fd7966e..af347b5b17fa8c 100644\n--- a/net/tls/tls_sw.c\n+++ b/net/tls/tls_sw.c\n@@ -2049,14 +2049,21 @@ ssize_t tls_sw_splice_read(struct socket *sock,  loff_t *ppos,\n \tgoto splice_read_end;\n }\n \n-int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n-\t\t     sk_read_actor_t read_actor)\n+/* Consecutive empty data records deliver no bytes; cap them per\n+ * call so a peer streaming them cannot hold the socket lock here.\n+ */\n+#define TLS_RX_NODATA_LIMIT 16\n+\n+static int __tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n+\t\t\t      sk_read_actor_t read_actor,\n+\t\t\t      sk_read_rectype_actor_t rectype_actor)\n {\n \tstruct tls_context *tls_ctx = tls_get_ctx(sk);\n \tstruct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx);\n \tstruct tls_prot_info *prot = \u0026tls_ctx-\u003eprot_info;\n \tstruct strp_msg *rxm = NULL;\n \tstruct sk_buff *skb = NULL;\n+\tunsigned int nodata_count = 0;\n \tstruct sk_psock *psock;\n \tsize_t flushed_at = 0;\n \tbool released = true;\n@@ -2109,10 +2116,27 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n \t\t\ttls_rx_rec_done(ctx);\n \t\t}\n \n-\t\t/* read_sock does not support reading control messages */\n+\t\t/* Control records (alerts, handshake) reach a consumer\n+\t\t * only through rectype_actor; without one, read_sock\n+\t\t * rejects them.\n+\t\t */\n \t\tif (tlm-\u003econtrol != TLS_RECORD_TYPE_DATA) {\n-\t\t\terr = -EINVAL;\n-\t\t\tgoto read_sock_requeue;\n+\t\t\tif (!rectype_actor) {\n+\t\t\t\terr = -EINVAL;\n+\t\t\t\tgoto read_sock_requeue;\n+\t\t\t}\n+\t\t\terr = rectype_actor(desc, skb, rxm-\u003eoffset,\n+\t\t\t\t\t rxm-\u003efull_len,\n+\t\t\t\t\t tlm-\u003econtrol);\n+\t\t\tif (err \u003c 0)\n+\t\t\t\tgoto read_sock_requeue;\n+\t\t\terr = 0;\n+\t\t\t/* rectype_actor consumes the whole record; no partial path */\n+\t\t\tconsume_skb(skb);\n+\t\t\tskb = NULL;\n+\t\t\tif (++nodata_count \u003e= TLS_RX_NODATA_LIMIT)\n+\t\t\t\tbreak;\n+\t\t\tcontinue;\n \t\t}\n \n \t\t/* An empty data record (legal in TLS 1.3) gives a zero\n@@ -2122,7 +2146,13 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n \t\t * here instead.\n \t\t */\n \t\tif (rxm-\u003efull_len == 0) {\n+\t\t\terr = 0;\n \t\t\tconsume_skb(skb);\n+\t\t\t/* tls_rx_reader_release() announces any parsed record\n+\t\t\t * on exit, so returning 0 here cannot strand it.\n+\t\t\t */\n+\t\t\tif (++nodata_count \u003e= TLS_RX_NODATA_LIMIT)\n+\t\t\t\tbreak;\n \t\t\tcontinue;\n \t\t}\n \n@@ -2133,6 +2163,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n \t\t\tgoto read_sock_requeue;\n \t\t}\n \t\tcopied += used;\n+\t\tnodata_count = 0;\n \t\tif (used \u003c rxm-\u003efull_len) {\n \t\t\trxm-\u003eoffset += used;\n \t\t\trxm-\u003efull_len -= used;\n@@ -2151,6 +2182,19 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n \tgoto read_sock_end;\n }\n \n+int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n+\t\t     sk_read_actor_t read_actor)\n+{\n+\treturn __tls_sw_read_sock(sk, desc, read_actor, NULL);\n+}\n+\n+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,\n+\t\t\t     sk_read_actor_t read_actor,\n+\t\t\t     sk_read_rectype_actor_t rectype_actor)\n+{\n+\treturn __tls_sw_read_sock(sk, desc, read_actor, rectype_actor);\n+}\n+\n bool tls_sw_sock_is_readable(struct sock *sk)\n {\n \tstruct tls_context *tls_ctx = tls_get_ctx(sk);\ndiff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c\nindex cbdd3ea28b998f..8136306b5caa77 100644\n--- a/tools/testing/selftests/net/tls.c\n+++ b/tools/testing/selftests/net/tls.c\n@@ -427,7 +427,8 @@ FIXTURE_SETUP(tls)\n \n FIXTURE_TEARDOWN(tls)\n {\n-\tclose(self-\u003efd);\n+\tif (self-\u003efd \u003e= 0)\n+\t\tclose(self-\u003efd);\n \tclose(self-\u003ecfd);\n }\n \n@@ -897,6 +898,114 @@ TEST_F(tls, splice_dec_cmsg_to_pipe)\n \tEXPECT_EQ(memcmp(test_str, buf, send_len), 0);\n }\n \n+/* Verify splice handles data-control-data: splice reads the data\n+ * records successfully while the intervening control record must\n+ * be drained via recvmsg before splice can continue.\n+ */\n+TEST_F(tls, splice_data_cmsg_data)\n+{\n+\tchar mem_send[TLS_PAYLOAD_MAX_LEN];\n+\tchar mem_recv[TLS_PAYLOAD_MAX_LEN];\n+\tint send_len = 4096;\n+\tchar *ctrl_str = \"control\";\n+\tint ctrl_len = strlen(ctrl_str) + 1;\n+\tchar ctrl_buf[8];\n+\tint p[2];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tmemrnd(mem_send, sizeof(mem_send));\n+\n+\tASSERT_GE(pipe(p), 0);\n+\n+\t/* Send: data, control, data */\n+\tEXPECT_EQ(send(self-\u003efd, mem_send, send_len, 0), send_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl_str, ctrl_len, 0),\n+\t\t  ctrl_len);\n+\tEXPECT_EQ(send(self-\u003efd, \u0026mem_send[send_len], send_len, 0), send_len);\n+\n+\t/* Splice first data record */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), send_len);\n+\tEXPECT_EQ(read(p[0], mem_recv, send_len), send_len);\n+\tEXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);\n+\n+\t/* Splice hits control record, fails */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), -1);\n+\tEXPECT_EQ(errno, EINVAL);\n+\n+\t/* Drain the control record via recvmsg */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),\n+\t\t  ctrl_len);\n+\tEXPECT_EQ(memcmp(ctrl_str, ctrl_buf, ctrl_len), 0);\n+\n+\t/* Splice second data record */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), send_len);\n+\tEXPECT_EQ(read(p[0], mem_recv, send_len), send_len);\n+\tEXPECT_EQ(memcmp(\u0026mem_send[send_len], mem_recv, send_len), 0);\n+}\n+\n+/* Verify that multiple consecutive control records between data\n+ * records can each be drained individually, and splice resumes\n+ * afterward.  The two control records use different content types\n+ * to verify type preservation across the splice boundary.\n+ */\n+TEST_F(tls, splice_multi_cmsg_data)\n+{\n+\tchar mem_send[TLS_PAYLOAD_MAX_LEN];\n+\tchar mem_recv[TLS_PAYLOAD_MAX_LEN];\n+\tint send_len = 4096;\n+\tchar *ctrl1 = \"alert1\";\n+\tchar *ctrl2 = \"alert2\";\n+\tint ctrl_len = strlen(ctrl1) + 1;\n+\tchar ctrl_buf[7];\n+\tint p[2];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tmemrnd(mem_send, sizeof(mem_send));\n+\n+\tASSERT_GE(pipe(p), 0);\n+\n+\t/* Send: data, control(100), control(200), data */\n+\tEXPECT_EQ(send(self-\u003efd, mem_send, send_len, 0), send_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl1, ctrl_len, 0), ctrl_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 200, ctrl2, ctrl_len, 0), ctrl_len);\n+\tEXPECT_EQ(send(self-\u003efd, \u0026mem_send[send_len], send_len, 0), send_len);\n+\n+\t/* Splice first data */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), send_len);\n+\tEXPECT_EQ(read(p[0], mem_recv, send_len), send_len);\n+\tEXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);\n+\n+\t/* Splice fails on first control record */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), -1);\n+\tEXPECT_EQ(errno, EINVAL);\n+\n+\t/* Drain first control (type 100) */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),\n+\t\t  ctrl_len);\n+\tEXPECT_EQ(memcmp(ctrl1, ctrl_buf, ctrl_len), 0);\n+\n+\t/* Splice fails on second control record */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), -1);\n+\tEXPECT_EQ(errno, EINVAL);\n+\n+\t/* Drain second control (type 200) */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 200,\n+\t\t\t\tctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),\n+\t\t  ctrl_len);\n+\tEXPECT_EQ(memcmp(ctrl2, ctrl_buf, ctrl_len), 0);\n+\n+\t/* Splice second data */\n+\tEXPECT_EQ(splice(self-\u003ecfd, NULL, p[1], NULL, send_len, 0), send_len);\n+\tEXPECT_EQ(read(p[0], mem_recv, send_len), send_len);\n+\tEXPECT_EQ(memcmp(\u0026mem_send[send_len], mem_recv, send_len), 0);\n+}\n+\n TEST_F(tls, recv_and_splice)\n {\n \tint send_len = TLS_PAYLOAD_MAX_LEN;\n@@ -1682,6 +1791,193 @@ TEST_F(tls, data_control_data)\n \tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), MSG_PEEK), send_len);\n }\n \n+/* Fully consume a data-control-data sequence.  The existing\n+ * data_control_data test only peeks; this exercises complete\n+ * record delivery through recv and recvmsg.\n+ */\n+TEST_F(tls, recv_data_cmsg_data)\n+{\n+\tchar *data1 = \"first_data\";\n+\tchar *ctrl = \"ctrl_msg\";\n+\tchar *data2 = \"second_data\";\n+\tint d1_len = strlen(data1) + 1;\n+\tint c_len = strlen(ctrl) + 1;\n+\tint d2_len = strlen(data2) + 1;\n+\tchar buf[20];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tEXPECT_EQ(send(self-\u003efd, data1, d1_len, 0), d1_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl, c_len, 0), c_len);\n+\tEXPECT_EQ(send(self-\u003efd, data2, d2_len, 0), d2_len);\n+\n+\t/* First data record */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), MSG_WAITALL), d1_len);\n+\tEXPECT_EQ(memcmp(buf, data1, d1_len), 0);\n+\n+\t/* recv without cmsg buffer fails on control record */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), 0), -1);\n+\tEXPECT_EQ(errno, EIO);\n+\n+\t/* Drain control via recvmsg */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), MSG_WAITALL), c_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl, c_len), 0);\n+\n+\t/* Second data record */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, d2_len, MSG_WAITALL), d2_len);\n+\tEXPECT_EQ(memcmp(buf, data2, d2_len), 0);\n+}\n+\n+/* Peek at an interleaved control record after the preceding data\n+ * record has been consumed, then consume it.  MSG_PEEK exposes the\n+ * control record's type without consuming it.\n+ */\n+TEST_F(tls, peek_cmsg_after_data)\n+{\n+\tchar *data = \"leading\";\n+\tchar *ctrl = \"middle\";\n+\tchar *tail = \"trailing\";\n+\tint d_len = strlen(data) + 1;\n+\tint c_len = strlen(ctrl) + 1;\n+\tint t_len = strlen(tail) + 1;\n+\tchar buf[20];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tEXPECT_EQ(send(self-\u003efd, data, d_len, 0), d_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl, c_len, 0), c_len);\n+\tEXPECT_EQ(send(self-\u003efd, tail, t_len, 0), t_len);\n+\n+\t/* Consume leading data */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), MSG_WAITALL), d_len);\n+\tEXPECT_EQ(memcmp(buf, data, d_len), 0);\n+\n+\t/* Peek at the control record */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), MSG_PEEK), c_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl, c_len), 0);\n+\n+\t/* Consume the control record */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), 0), c_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl, c_len), 0);\n+\n+\t/* Trailing data */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, t_len, MSG_WAITALL), t_len);\n+\tEXPECT_EQ(memcmp(buf, tail, t_len), 0);\n+}\n+\n+/* Control record as the first record in the stream, followed by\n+ * data.  The control record must be drained before the data record\n+ * becomes available.\n+ */\n+TEST_F(tls, cmsg_before_data)\n+{\n+\tchar *ctrl = \"alert\";\n+\tchar *data = \"payload\";\n+\tint c_len = strlen(ctrl) + 1;\n+\tint d_len = strlen(data) + 1;\n+\tchar buf[20];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl, c_len, 0), c_len);\n+\tEXPECT_EQ(send(self-\u003efd, data, d_len, 0), d_len);\n+\n+\t/* recv without cmsg fails */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), 0), -1);\n+\tEXPECT_EQ(errno, EIO);\n+\n+\t/* Drain control via recvmsg */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), MSG_WAITALL), c_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl, c_len), 0);\n+\n+\t/* Data follows */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, d_len, MSG_WAITALL), d_len);\n+\tEXPECT_EQ(memcmp(buf, data, d_len), 0);\n+}\n+\n+/* Two different control record types interleaved with data.\n+ * Each control record is delivered with its own type preserved;\n+ * verify both types arrive intact.\n+ */\n+TEST_F(tls, mixed_control_types)\n+{\n+\tchar *data1 = \"data1\";\n+\tchar *ctrl1 = \"handshake\";\n+\tchar *ctrl2 = \"alert_msg\";\n+\tchar *data2 = \"data2\";\n+\tint d1_len = strlen(data1) + 1;\n+\tint c1_len = strlen(ctrl1) + 1;\n+\tint c2_len = strlen(ctrl2) + 1;\n+\tint d2_len = strlen(data2) + 1;\n+\tchar buf[20];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tEXPECT_EQ(send(self-\u003efd, data1, d1_len, 0), d1_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl1, c1_len, 0), c1_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 200, ctrl2, c2_len, 0), c2_len);\n+\tEXPECT_EQ(send(self-\u003efd, data2, d2_len, 0), d2_len);\n+\n+\t/* First data */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), MSG_WAITALL), d1_len);\n+\tEXPECT_EQ(memcmp(buf, data1, d1_len), 0);\n+\n+\t/* First control (type 100) */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), MSG_WAITALL), c1_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl1, c1_len), 0);\n+\n+\t/* Second control (type 200) */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 200,\n+\t\t\t\tbuf, sizeof(buf), MSG_WAITALL), c2_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl2, c2_len), 0);\n+\n+\t/* Second data */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, d2_len, MSG_WAITALL), d2_len);\n+\tEXPECT_EQ(memcmp(buf, data2, d2_len), 0);\n+}\n+\n+/* Trailing control record with no data following it.  The sender\n+ * closes the connection after the control record; the receiver\n+ * drains the control and then observes EOF.\n+ */\n+TEST_F(tls, data_cmsg_eof)\n+{\n+\tchar *data = \"payload\";\n+\tchar *ctrl = \"final\";\n+\tint d_len = strlen(data) + 1;\n+\tint c_len = strlen(ctrl) + 1;\n+\tchar buf[20];\n+\n+\tif (self-\u003enotls)\n+\t\tSKIP(return, \"no TLS support\");\n+\n+\tEXPECT_EQ(send(self-\u003efd, data, d_len, 0), d_len);\n+\tEXPECT_EQ(tls_send_cmsg(self-\u003efd, 100, ctrl, c_len, 0), c_len);\n+\tEXPECT_EQ(close(self-\u003efd), 0);\n+\tself-\u003efd = -1;\n+\n+\t/* Consume data */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), MSG_WAITALL), d_len);\n+\tEXPECT_EQ(memcmp(buf, data, d_len), 0);\n+\n+\t/* Drain trailing control */\n+\tEXPECT_EQ(tls_recv_cmsg(_metadata, self-\u003ecfd, 100,\n+\t\t\t\tbuf, sizeof(buf), 0), c_len);\n+\tEXPECT_EQ(memcmp(buf, ctrl, c_len), 0);\n+\n+\t/* Next recv returns EOF */\n+\tEXPECT_EQ(recv(self-\u003ecfd, buf, sizeof(buf), 0), 0);\n+}\n+\n TEST_F(tls, shutdown)\n {\n \tchar const *test_str = \"test_read\";\n"
}

2/1 2026/07/20 22:04 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "NFSD",
    "TLS"
  ],
  "FocusSymbols": [
    "svc_tcp_recv_actor",
    "svc_tcp_rectype_actor",
    "svc_tcp_recvfrom",
    "tls_sw_read_sock_rectype",
    "__tls_sw_read_sock"
  ],
  "Reasoning": "The patch introduces a new proto_ops callback `read_sock_rectype` to allow reading non-data TLS records (like alerts and handshake messages) directly from the socket. It implements this in the TLS software path (`__tls_sw_read_sock`, `tls_sw_read_sock_rectype`) and uses it in the SUNRPC server TCP socket code (`svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `svc_tcp_recvfrom`). This is a functional change in core networking and RPC code that should be fuzzed to ensure no regressions or vulnerabilities are introduced in the TLS record parsing and RPC message assembly logic.",
  "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).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 2c44b0239b317ff488181d7925c4ecb05ad186b2
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 20 22:04:50 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/net.h b/include/linux/net.h
index 277188a40c72e3..7a19a743a61776 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -198,6 +198,13 @@ struct sk_buff;
 struct proto_accept_arg;
 typedef int (*sk_read_actor_t)(read_descriptor_t *, struct sk_buff *,
 			       unsigned int, size_t);
+/* rectype carries the transport record type, for example a
+ * TLS_RECORD_TYPE_* value.
+ */
+typedef int (*sk_read_rectype_actor_t)(read_descriptor_t *,
+				       struct sk_buff *,
+				       unsigned int, size_t,
+				       u8 rectype);
 typedef int (*skb_read_actor_t)(struct sock *, struct sk_buff *);
 
 
@@ -264,6 +271,27 @@ struct proto_ops {
 	 */
 	int		(*read_sock)(struct sock *sk, read_descriptor_t *desc,
 				     sk_read_actor_t recv_actor);
+	/*
+	 * read_sock_rectype splits delivery across two callbacks:
+	 * recv_actor for data records, per the sk_read_actor_t
+	 * convention, and rectype_actor for all other records,
+	 * with rectype identifying each. A NULL rectype_actor
+	 * leaves non-data records pending. rectype_actor returns 0
+	 * to consume a record or negative to leave it pending for
+	 * redelivery and stop delivery; the negative return is a
+	 * backpressure signal, not a fatal error. Both callbacks
+	 * report errors and early stop the way recv_actor does:
+	 * by setting desc->count to 0 and recording the reason in
+	 * desc->error, per the read_descriptor_t convention and
+	 * independent of the return value. The return value reports
+	 * only data bytes consumed by recv_actor; the caller
+	 * detects an error or early stop via desc->count and
+	 * desc->error.
+	 */
+	int		(*read_sock_rectype)(struct sock *sk,
+					     read_descriptor_t *desc,
+					     sk_read_actor_t recv_actor,
+					     sk_read_rectype_actor_t rectype_actor);
 	/* This is different from read_sock(), it reads an entire skb at a time. */
 	int		(*read_skb)(struct sock *sk, skb_read_actor_t recv_actor);
 	int		(*sendmsg_locked)(struct sock *sk, struct msghdr *msg,
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 50e5e7f5b762de..9b9e0da9e73cd6 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -8,15 +8,6 @@
  * evenly when servicing a single client. May need to modify the
  * svc_xprt_enqueue procedure...
  *
- * TCP support is largely untested and may be a little slow. The problem
- * is that we currently do two separate recvfrom's, one for the 4-byte
- * record length, and the second for the actual record. This could possibly
- * be improved by always reading a minimum size of around 100 bytes and
- * tucking any superfluous bytes away in a temporary store. Still, that
- * leaves write requests out in the rain. An alternative may be to peek at
- * the first skb in the queue, and if it matches the next TCP sequence
- * number, to extract the record marker. Yuck.
- *
  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
  */
 
@@ -238,138 +229,6 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining)
 	return len;
 }
 
-static int
-svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
-			  struct cmsghdr *cmsg, int ret)
-{
-	u8 content_type = tls_get_record_type(sock->sk, cmsg);
-	u8 level, description;
-
-	switch (content_type) {
-	case 0:
-		break;
-	case TLS_RECORD_TYPE_DATA:
-		/* TLS sets EOR at the end of each application data
-		 * record, even though there might be more frames
-		 * waiting to be decrypted.
-		 */
-		msg->msg_flags &= ~MSG_EOR;
-		break;
-	case TLS_RECORD_TYPE_ALERT:
-		tls_alert_recv(sock->sk, msg, &level, &description);
-		ret = (level == TLS_ALERT_LEVEL_FATAL) ?
-			-ENOTCONN : -EAGAIN;
-		break;
-	default:
-		/* discard this record type */
-		ret = -EAGAIN;
-	}
-	return ret;
-}
-
-static int
-svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
-{
-	union {
-		struct cmsghdr	cmsg;
-		u8		buf[CMSG_SPACE(sizeof(u8))];
-	} u;
-	u8 alert[2];
-	struct kvec alert_kvec = {
-		.iov_base = alert,
-		.iov_len = sizeof(alert),
-	};
-	struct msghdr msg = {
-		.msg_flags = *msg_flags,
-		.msg_control = &u,
-		.msg_controllen = sizeof(u),
-	};
-	int ret;
-
-	iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
-		      alert_kvec.iov_len);
-	ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT);
-	if (ret > 0 &&
-	    tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) {
-		iov_iter_revert(&msg.msg_iter, ret);
-		ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
-	}
-	return ret;
-}
-
-static int
-svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)
-{
-	int ret;
-	struct socket *sock = svsk->sk_sock;
-
-	ret = sock_recvmsg(sock, msg, MSG_DONTWAIT);
-	if (msg->msg_flags & MSG_CTRUNC) {
-		msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
-		if (ret == 0 || ret == -EIO)
-			ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags);
-	}
-	return ret;
-}
-
-#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE
-static void svc_flush_bvec(const struct bio_vec *bvec, size_t size, size_t seek)
-{
-	struct bvec_iter bi = {
-		.bi_size	= size + seek,
-	};
-	struct bio_vec bv;
-
-	bvec_iter_advance(bvec, &bi, seek & PAGE_MASK);
-	for_each_bvec(bv, bvec, bi, bi)
-		flush_dcache_page(bv.bv_page);
-}
-#else
-static inline void svc_flush_bvec(const struct bio_vec *bvec, size_t size,
-				  size_t seek)
-{
-}
-#endif
-
-/*
- * Read from @rqstp's transport socket. The incoming message fills whole
- * pages in @rqstp's rq_pages array until the last page of the message
- * has been received into a partial page.
- */
-static ssize_t svc_tcp_read_msg(struct svc_rqst *rqstp, size_t buflen,
-				size_t seek)
-{
-	struct svc_sock *svsk =
-		container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
-	struct bio_vec *bvec = rqstp->rq_bvec;
-	struct msghdr msg = { NULL };
-	unsigned int i;
-	ssize_t len;
-	size_t t;
-
-	clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
-
-	for (i = 0, t = 0; t < buflen; i++, t += PAGE_SIZE)
-		bvec_set_page(&bvec[i], rqstp->rq_pages[i], PAGE_SIZE, 0);
-
-	iov_iter_bvec(&msg.msg_iter, ITER_DEST, bvec, i, buflen);
-	if (seek) {
-		iov_iter_advance(&msg.msg_iter, seek);
-		buflen -= seek;
-	}
-	len = svc_tcp_sock_recvmsg(svsk, &msg);
-	if (len > 0)
-		svc_flush_bvec(bvec, len, seek);
-
-	/* If we read a full record, then assume there may be more
-	 * data to read (stream based sockets only!)
-	 */
-	if (len == buflen)
-		set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
-
-	return len;
-}
-
 /*
  * Set socket snd and rcv buffer lengths
  */
@@ -1048,50 +907,6 @@ static void svc_tcp_clear_pages(struct svc_sock *svsk)
 	svsk->sk_datalen = 0;
 }
 
-/*
- * Receive fragment record header into sk_marker.
- */
-static ssize_t svc_tcp_read_marker(struct svc_sock *svsk,
-				   struct svc_rqst *rqstp)
-{
-	ssize_t want, len;
-
-	/* If we haven't gotten the record length yet,
-	 * get the next four bytes.
-	 */
-	if (svsk->sk_tcplen < sizeof(rpc_fraghdr)) {
-		struct msghdr	msg = { NULL };
-		struct kvec	iov;
-
-		want = sizeof(rpc_fraghdr) - svsk->sk_tcplen;
-		iov.iov_base = ((char *)&svsk->sk_marker) + svsk->sk_tcplen;
-		iov.iov_len  = want;
-		iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, want);
-		len = svc_tcp_sock_recvmsg(svsk, &msg);
-		if (len < 0)
-			return len;
-		svsk->sk_tcplen += len;
-		if (len < want) {
-			/* call again to read the remaining bytes */
-			goto err_short;
-		}
-		trace_svcsock_marker(&svsk->sk_xprt, svsk->sk_marker);
-		if (svc_sock_reclen(svsk) + svsk->sk_datalen >
-		    svsk->sk_xprt.xpt_server->sv_max_mesg)
-			goto err_too_large;
-	}
-	return svc_sock_reclen(svsk);
-
-err_too_large:
-	net_notice_ratelimited("svc: %s oversized RPC fragment (%u octets) from %pISpc\n",
-			       svsk->sk_xprt.xpt_server->sv_name,
-			       svc_sock_reclen(svsk),
-			       (struct sockaddr *)&svsk->sk_xprt.xpt_remote);
-	svc_xprt_deferred_close(&svsk->sk_xprt);
-err_short:
-	return -EAGAIN;
-}
-
 static int receive_cb_reply(struct svc_sock *svsk, struct svc_rqst *rqstp)
 {
 	struct rpc_xprt *bc_xprt = svsk->sk_xprt.xpt_bc_xprt;
@@ -1134,16 +949,144 @@ static void svc_tcp_fragment_received(struct svc_sock *svsk)
 	svsk->sk_marker = xdr_zero;
 }
 
+/*
+ * read_sock data actor: receives application data from the
+ * transport socket, parsing the RPC record stream (fragment
+ * headers and message bodies) and assembling complete RPC
+ * messages into rqstp->rq_pages.
+ */
+static int svc_tcp_recv_actor(read_descriptor_t *desc,
+			      struct sk_buff *skb,
+			      unsigned int offset, size_t len)
+{
+	struct svc_rqst *rqstp = desc->arg.data;
+	struct svc_sock *svsk =
+		container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
+	size_t reclen, received, want, take, done, n;
+	size_t consumed = 0;
+
+	if (!desc->count)
+		return 0;
+
+	if (svsk->sk_tcplen < sizeof(rpc_fraghdr)) {
+		want = sizeof(rpc_fraghdr) - svsk->sk_tcplen;
+		n = min(want, len);
+
+		if (skb_copy_bits(skb, offset,
+				  (char *)&svsk->sk_marker +
+					svsk->sk_tcplen, n))
+			goto fault;
+		svsk->sk_tcplen += n;
+		offset += n;
+		len -= n;
+		consumed += n;
+
+		if (svsk->sk_tcplen < sizeof(rpc_fraghdr))
+			return consumed;
+
+		trace_svcsock_marker(&svsk->sk_xprt, svsk->sk_marker);
+		if (svc_sock_reclen(svsk) + svsk->sk_datalen >
+		    svsk->sk_xprt.xpt_server->sv_max_mesg) {
+			net_notice_ratelimited("svc: %s oversized RPC fragment (%u octets) from %pISpc\n",
+					       svsk->sk_xprt.xpt_server->sv_name,
+					       svc_sock_reclen(svsk),
+					       (struct sockaddr *)&svsk->sk_xprt.xpt_remote);
+			desc->error = -EMSGSIZE;
+			desc->count = 0;
+			return consumed;
+		}
+	}
+
+	reclen = svc_sock_reclen(svsk);
+	received = svsk->sk_tcplen - sizeof(rpc_fraghdr);
+	want = reclen - received;
+	take = min(want, len);
+	done = 0;
+
+	while (done < take) {
+		unsigned int pg = svsk->sk_datalen >> PAGE_SHIFT;
+		unsigned int pg_off = svsk->sk_datalen & (PAGE_SIZE - 1);
+		size_t chunk = min(take - done,
+				   PAGE_SIZE - (size_t)pg_off);
+
+		if (skb_copy_bits(skb, offset,
+				  page_address(rqstp->rq_pages[pg]) + pg_off,
+				  chunk))
+			goto fault;
+		flush_dcache_page(rqstp->rq_pages[pg]);
+		offset += chunk;
+		done += chunk;
+		svsk->sk_datalen += chunk;
+	}
+	svsk->sk_tcplen += take;
+	consumed += take;
+
+	if (svsk->sk_tcplen - sizeof(rpc_fraghdr) >= reclen) {
+		if (svc_sock_final_rec(svsk))
+			desc->count = 0;
+		else
+			svc_tcp_fragment_received(svsk);
+	}
+
+	return consumed;
+
+fault:
+	desc->error = -EFAULT;
+	desc->count = 0;
+	return consumed;
+}
+
+/*
+ * read_sock_rectype non-data record actor: receives non-data TLS
+ * records (alerts, handshake messages) and translates them into
+ * transport-level actions.
+ *
+ * Returns 0 to consume the record and allow the TLS layer to
+ * continue delivering subsequent records. A negative return
+ * causes the TLS layer to requeue the skb on its rx_list,
+ * blocking all further record delivery on this connection.
+ */
+static int svc_tcp_rectype_actor(read_descriptor_t *desc,
+				 struct sk_buff *skb,
+				 unsigned int offset, size_t len,
+				 u8 rectype)
+{
+	struct svc_rqst *rqstp = desc->arg.data;
+	struct svc_sock *svsk =
+		container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
+
+	switch (rectype) {
+	case TLS_RECORD_TYPE_ALERT: {
+		u8 alert[2] = {}, level, description;
+		struct kvec kvec = {
+			.iov_base = alert,
+			.iov_len = sizeof(alert),
+		};
+		struct msghdr msg = {};
+
+		if (skb_copy_bits(skb, offset, alert, min(len, sizeof(alert))))
+			break;
+		iov_iter_kvec(&msg.msg_iter, ITER_DEST, &kvec, 1, sizeof(alert));
+		tls_alert_recv(svsk->sk_sk, &msg, &level, &description);
+		if (level == TLS_ALERT_LEVEL_FATAL) {
+			svc_xprt_deferred_close(&svsk->sk_xprt);
+			desc->error = -ENOTCONN;
+			desc->count = 0;
+		}
+		break;
+	}
+	default:
+		break;
+	}
+	return 0;
+}
+
 /**
  * svc_tcp_recvfrom - Receive data from a TCP socket
  * @rqstp: request structure into which to receive an RPC Call
  *
  * Called in a loop when XPT_DATA has been set.
  *
- * Read the 4-byte stream record marker, then use the record length
- * in that marker to set up exactly the resources needed to receive
- * the next RPC message into @rqstp.
- *
  * Returns:
  *   On success, the number of bytes in a received RPC Call, or
  *   %0 if a complete RPC Call message was not ready to return
@@ -1157,26 +1100,40 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
 	struct svc_sock	*svsk =
 		container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
 	struct svc_serv	*serv = svsk->sk_xprt.xpt_server;
-	size_t want, base;
+	struct sock	*sk = svsk->sk_sk;
+	read_descriptor_t desc = {
+		.arg.data = rqstp,
+	};
 	ssize_t len;
 	__be32 *p;
 	__be32 calldir;
 
 	clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
-	len = svc_tcp_read_marker(svsk, rqstp);
-	if (len < 0)
-		goto error;
 
-	base = svc_tcp_restore_pages(svsk, rqstp);
-	want = len - (svsk->sk_tcplen - sizeof(rpc_fraghdr));
-	len = svc_tcp_read_msg(rqstp, base + want, base);
-	if (len >= 0) {
-		trace_svcsock_tcp_recv(&svsk->sk_xprt, len);
-		svsk->sk_tcplen += len;
-		svsk->sk_datalen += len;
+	svc_tcp_restore_pages(svsk, rqstp);
+	rqstp->rq_arg.head[0].iov_base = page_address(rqstp->rq_pages[0]);
+
+	desc.count = serv->sv_max_mesg;
+	lock_sock(sk);
+	if (svsk->sk_sock->ops->read_sock_rectype)
+		len = svsk->sk_sock->ops->read_sock_rectype(sk, &desc,
+							    svc_tcp_recv_actor,
+							    svc_tcp_rectype_actor);
+	else
+		len = svsk->sk_sock->ops->read_sock(sk, &desc,
+						     svc_tcp_recv_actor);
+	release_sock(sk);
+
+	if (desc.error < 0) {
+		len = desc.error;
+		goto err_discard;
 	}
-	if (len != want || !svc_sock_final_rec(svsk))
+	if (desc.count != 0) {
+		if (len > 0)
+			set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
 		goto err_incomplete;
+	}
+
 	if (svsk->sk_datalen < 8)
 		goto err_nuts;
 
@@ -1185,8 +1142,10 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
 	if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0].iov_len) {
 		rqstp->rq_arg.head[0].iov_len = rqstp->rq_arg.len;
 		rqstp->rq_arg.page_len = 0;
-	} else
-		rqstp->rq_arg.page_len = rqstp->rq_arg.len - rqstp->rq_arg.head[0].iov_len;
+	} else {
+		rqstp->rq_arg.page_len = rqstp->rq_arg.len -
+			rqstp->rq_arg.head[0].iov_len;
+	}
 
 	rqstp->rq_xprt_ctxt   = NULL;
 	rqstp->rq_prot	      = IPPROTO_TCP;
@@ -1207,11 +1166,13 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
 	if (len < 0)
 		goto error;
 
+	trace_svcsock_tcp_recv(&svsk->sk_xprt, rqstp->rq_arg.len);
 	svc_xprt_copy_addrs(rqstp, &svsk->sk_xprt);
 	if (serv->sv_stats)
 		serv->sv_stats->nettcpcnt++;
 
 	svc_sock_secure_port(rqstp);
+	set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
 	svc_xprt_received(rqstp->rq_xprt);
 	return rqstp->rq_arg.len;
 
@@ -1219,9 +1180,7 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
 	svc_tcp_save_pages(svsk, rqstp);
 	if (len < 0 && len != -EAGAIN)
 		goto err_delete;
-	if (len == want)
-		svc_tcp_fragment_received(svsk);
-	else
+	if (svsk->sk_tcplen >= sizeof(rpc_fraghdr))
 		trace_svcsock_tcp_recv_short(&svsk->sk_xprt,
 				svc_sock_reclen(svsk),
 				svsk->sk_tcplen - sizeof(rpc_fraghdr));
@@ -1233,12 +1192,20 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
 	goto err_noclose;
 err_nuts:
 	svsk->sk_datalen = 0;
+	goto err_delete;
+err_discard:
+	/*
+	 * Clear sk_datalen so the teardown-time
+	 * svc_tcp_clear_pages() does not walk the emptied
+	 * svsk->sk_pages[].
+	 */
+	svsk->sk_datalen = 0;
 err_delete:
 	trace_svcsock_tcp_recv_err(&svsk->sk_xprt, len);
 	svc_xprt_deferred_close(&svsk->sk_xprt);
 err_noclose:
 	svc_xprt_received(rqstp->rq_xprt);
-	return 0;	/* record not complete */
+	return 0;
 }
 
 /*
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 60a37bdaaa2506..c21d2a985e1373 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -168,6 +168,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
 			   size_t len, unsigned int flags);
 int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 		     sk_read_actor_t read_actor);
+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,
+			     sk_read_actor_t read_actor,
+			     sk_read_rectype_actor_t rectype_actor);
 
 int tls_device_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);
 void tls_device_splice_eof(struct socket *sock);
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8c588cdab733da..4963e0caf6d53a 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -949,12 +949,17 @@ static void build_proto_ops(struct proto_ops ops[TLS_NUM_CONFIG][TLS_NUM_CONFIG]
 	ops[TLS_BASE][TLS_SW  ].splice_read	= tls_sw_splice_read;
 	ops[TLS_BASE][TLS_SW  ].poll		= tls_sk_poll;
 	ops[TLS_BASE][TLS_SW  ].read_sock	= tls_sw_read_sock;
+	ops[TLS_BASE][TLS_SW  ].read_sock_rectype	= tls_sw_read_sock_rectype;
 
 	ops[TLS_SW  ][TLS_SW  ] = ops[TLS_SW  ][TLS_BASE];
 	ops[TLS_SW  ][TLS_SW  ].splice_read	= tls_sw_splice_read;
 	ops[TLS_SW  ][TLS_SW  ].poll		= tls_sk_poll;
 	ops[TLS_SW  ][TLS_SW  ].read_sock	= tls_sw_read_sock;
+	ops[TLS_SW  ][TLS_SW  ].read_sock_rectype	= tls_sw_read_sock_rectype;
 
+	/* TLS_HW (device offload) RX entries inherit
+	 * read_sock{,_rectype} from SW via the struct copies below.
+	 */
 #ifdef CONFIG_TLS_DEVICE
 	ops[TLS_HW  ][TLS_BASE] = ops[TLS_BASE][TLS_BASE];
 
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index d4afc90fd7966e..af347b5b17fa8c 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -2049,14 +2049,21 @@ ssize_t tls_sw_splice_read(struct socket *sock,  loff_t *ppos,
 	goto splice_read_end;
 }
 
-int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
-		     sk_read_actor_t read_actor)
+/* Consecutive empty data records deliver no bytes; cap them per
+ * call so a peer streaming them cannot hold the socket lock here.
+ */
+#define TLS_RX_NODATA_LIMIT 16
+
+static int __tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
+			      sk_read_actor_t read_actor,
+			      sk_read_rectype_actor_t rectype_actor)
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
 	struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx);
 	struct tls_prot_info *prot = &tls_ctx->prot_info;
 	struct strp_msg *rxm = NULL;
 	struct sk_buff *skb = NULL;
+	unsigned int nodata_count = 0;
 	struct sk_psock *psock;
 	size_t flushed_at = 0;
 	bool released = true;
@@ -2109,10 +2116,27 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 			tls_rx_rec_done(ctx);
 		}
 
-		/* read_sock does not support reading control messages */
+		/* Control records (alerts, handshake) reach a consumer
+		 * only through rectype_actor; without one, read_sock
+		 * rejects them.
+		 */
 		if (tlm->control != TLS_RECORD_TYPE_DATA) {
-			err = -EINVAL;
-			goto read_sock_requeue;
+			if (!rectype_actor) {
+				err = -EINVAL;
+				goto read_sock_requeue;
+			}
+			err = rectype_actor(desc, skb, rxm->offset,
+					 rxm->full_len,
+					 tlm->control);
+			if (err < 0)
+				goto read_sock_requeue;
+			err = 0;
+			/* rectype_actor consumes the whole record; no partial path */
+			consume_skb(skb);
+			skb = NULL;
+			if (++nodata_count >= TLS_RX_NODATA_LIMIT)
+				break;
+			continue;
 		}
 
 		/* An empty data record (legal in TLS 1.3) gives a zero
@@ -2122,7 +2146,13 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 		 * here instead.
 		 */
 		if (rxm->full_len == 0) {
+			err = 0;
 			consume_skb(skb);
+			/* tls_rx_reader_release() announces any parsed record
+			 * on exit, so returning 0 here cannot strand it.
+			 */
+			if (++nodata_count >= TLS_RX_NODATA_LIMIT)
+				break;
 			continue;
 		}
 
@@ -2133,6 +2163,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 			goto read_sock_requeue;
 		}
 		copied += used;
+		nodata_count = 0;
 		if (used < rxm->full_len) {
 			rxm->offset += used;
 			rxm->full_len -= used;
@@ -2151,6 +2182,19 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 	goto read_sock_end;
 }
 
+int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
+		     sk_read_actor_t read_actor)
+{
+	return __tls_sw_read_sock(sk, desc, read_actor, NULL);
+}
+
+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,
+			     sk_read_actor_t read_actor,
+			     sk_read_rectype_actor_t rectype_actor)
+{
+	return __tls_sw_read_sock(sk, desc, read_actor, rectype_actor);
+}
+
 bool tls_sw_sock_is_readable(struct sock *sk)
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index cbdd3ea28b998f..8136306b5caa77 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -427,7 +427,8 @@ FIXTURE_SETUP(tls)
 
 FIXTURE_TEARDOWN(tls)
 {
-	close(self->fd);
+	if (self->fd >= 0)
+		close(self->fd);
 	close(self->cfd);
 }
 
@@ -897,6 +898,114 @@ TEST_F(tls, splice_dec_cmsg_to_pipe)
 	EXPECT_EQ(memcmp(test_str, buf, send_len), 0);
 }
 
+/* Verify splice handles data-control-data: splice reads the data
+ * records successfully while the intervening control record must
+ * be drained via recvmsg before splice can continue.
+ */
+TEST_F(tls, splice_data_cmsg_data)
+{
+	char mem_send[TLS_PAYLOAD_MAX_LEN];
+	char mem_recv[TLS_PAYLOAD_MAX_LEN];
+	int send_len = 4096;
+	char *ctrl_str = "control";
+	int ctrl_len = strlen(ctrl_str) + 1;
+	char ctrl_buf[8];
+	int p[2];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	memrnd(mem_send, sizeof(mem_send));
+
+	ASSERT_GE(pipe(p), 0);
+
+	/* Send: data, control, data */
+	EXPECT_EQ(send(self->fd, mem_send, send_len, 0), send_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl_str, ctrl_len, 0),
+		  ctrl_len);
+	EXPECT_EQ(send(self->fd, &mem_send[send_len], send_len, 0), send_len);
+
+	/* Splice first data record */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+	EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+	EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);
+
+	/* Splice hits control record, fails */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	/* Drain the control record via recvmsg */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+		  ctrl_len);
+	EXPECT_EQ(memcmp(ctrl_str, ctrl_buf, ctrl_len), 0);
+
+	/* Splice second data record */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+	EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+	EXPECT_EQ(memcmp(&mem_send[send_len], mem_recv, send_len), 0);
+}
+
+/* Verify that multiple consecutive control records between data
+ * records can each be drained individually, and splice resumes
+ * afterward.  The two control records use different content types
+ * to verify type preservation across the splice boundary.
+ */
+TEST_F(tls, splice_multi_cmsg_data)
+{
+	char mem_send[TLS_PAYLOAD_MAX_LEN];
+	char mem_recv[TLS_PAYLOAD_MAX_LEN];
+	int send_len = 4096;
+	char *ctrl1 = "alert1";
+	char *ctrl2 = "alert2";
+	int ctrl_len = strlen(ctrl1) + 1;
+	char ctrl_buf[7];
+	int p[2];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	memrnd(mem_send, sizeof(mem_send));
+
+	ASSERT_GE(pipe(p), 0);
+
+	/* Send: data, control(100), control(200), data */
+	EXPECT_EQ(send(self->fd, mem_send, send_len, 0), send_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl1, ctrl_len, 0), ctrl_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 200, ctrl2, ctrl_len, 0), ctrl_len);
+	EXPECT_EQ(send(self->fd, &mem_send[send_len], send_len, 0), send_len);
+
+	/* Splice first data */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+	EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+	EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);
+
+	/* Splice fails on first control record */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	/* Drain first control (type 100) */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+		  ctrl_len);
+	EXPECT_EQ(memcmp(ctrl1, ctrl_buf, ctrl_len), 0);
+
+	/* Splice fails on second control record */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	/* Drain second control (type 200) */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 200,
+				ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+		  ctrl_len);
+	EXPECT_EQ(memcmp(ctrl2, ctrl_buf, ctrl_len), 0);
+
+	/* Splice second data */
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+	EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+	EXPECT_EQ(memcmp(&mem_send[send_len], mem_recv, send_len), 0);
+}
+
 TEST_F(tls, recv_and_splice)
 {
 	int send_len = TLS_PAYLOAD_MAX_LEN;
@@ -1682,6 +1791,193 @@ TEST_F(tls, data_control_data)
 	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_PEEK), send_len);
 }
 
+/* Fully consume a data-control-data sequence.  The existing
+ * data_control_data test only peeks; this exercises complete
+ * record delivery through recv and recvmsg.
+ */
+TEST_F(tls, recv_data_cmsg_data)
+{
+	char *data1 = "first_data";
+	char *ctrl = "ctrl_msg";
+	char *data2 = "second_data";
+	int d1_len = strlen(data1) + 1;
+	int c_len = strlen(ctrl) + 1;
+	int d2_len = strlen(data2) + 1;
+	char buf[20];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	EXPECT_EQ(send(self->fd, data1, d1_len, 0), d1_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+	EXPECT_EQ(send(self->fd, data2, d2_len, 0), d2_len);
+
+	/* First data record */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d1_len);
+	EXPECT_EQ(memcmp(buf, data1, d1_len), 0);
+
+	/* recv without cmsg buffer fails on control record */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EIO);
+
+	/* Drain control via recvmsg */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), MSG_WAITALL), c_len);
+	EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+	/* Second data record */
+	EXPECT_EQ(recv(self->cfd, buf, d2_len, MSG_WAITALL), d2_len);
+	EXPECT_EQ(memcmp(buf, data2, d2_len), 0);
+}
+
+/* Peek at an interleaved control record after the preceding data
+ * record has been consumed, then consume it.  MSG_PEEK exposes the
+ * control record's type without consuming it.
+ */
+TEST_F(tls, peek_cmsg_after_data)
+{
+	char *data = "leading";
+	char *ctrl = "middle";
+	char *tail = "trailing";
+	int d_len = strlen(data) + 1;
+	int c_len = strlen(ctrl) + 1;
+	int t_len = strlen(tail) + 1;
+	char buf[20];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+	EXPECT_EQ(send(self->fd, tail, t_len, 0), t_len);
+
+	/* Consume leading data */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d_len);
+	EXPECT_EQ(memcmp(buf, data, d_len), 0);
+
+	/* Peek at the control record */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), MSG_PEEK), c_len);
+	EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+	/* Consume the control record */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), 0), c_len);
+	EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+	/* Trailing data */
+	EXPECT_EQ(recv(self->cfd, buf, t_len, MSG_WAITALL), t_len);
+	EXPECT_EQ(memcmp(buf, tail, t_len), 0);
+}
+
+/* Control record as the first record in the stream, followed by
+ * data.  The control record must be drained before the data record
+ * becomes available.
+ */
+TEST_F(tls, cmsg_before_data)
+{
+	char *ctrl = "alert";
+	char *data = "payload";
+	int c_len = strlen(ctrl) + 1;
+	int d_len = strlen(data) + 1;
+	char buf[20];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+	EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+
+	/* recv without cmsg fails */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EIO);
+
+	/* Drain control via recvmsg */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), MSG_WAITALL), c_len);
+	EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+	/* Data follows */
+	EXPECT_EQ(recv(self->cfd, buf, d_len, MSG_WAITALL), d_len);
+	EXPECT_EQ(memcmp(buf, data, d_len), 0);
+}
+
+/* Two different control record types interleaved with data.
+ * Each control record is delivered with its own type preserved;
+ * verify both types arrive intact.
+ */
+TEST_F(tls, mixed_control_types)
+{
+	char *data1 = "data1";
+	char *ctrl1 = "handshake";
+	char *ctrl2 = "alert_msg";
+	char *data2 = "data2";
+	int d1_len = strlen(data1) + 1;
+	int c1_len = strlen(ctrl1) + 1;
+	int c2_len = strlen(ctrl2) + 1;
+	int d2_len = strlen(data2) + 1;
+	char buf[20];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	EXPECT_EQ(send(self->fd, data1, d1_len, 0), d1_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl1, c1_len, 0), c1_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 200, ctrl2, c2_len, 0), c2_len);
+	EXPECT_EQ(send(self->fd, data2, d2_len, 0), d2_len);
+
+	/* First data */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d1_len);
+	EXPECT_EQ(memcmp(buf, data1, d1_len), 0);
+
+	/* First control (type 100) */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), MSG_WAITALL), c1_len);
+	EXPECT_EQ(memcmp(buf, ctrl1, c1_len), 0);
+
+	/* Second control (type 200) */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 200,
+				buf, sizeof(buf), MSG_WAITALL), c2_len);
+	EXPECT_EQ(memcmp(buf, ctrl2, c2_len), 0);
+
+	/* Second data */
+	EXPECT_EQ(recv(self->cfd, buf, d2_len, MSG_WAITALL), d2_len);
+	EXPECT_EQ(memcmp(buf, data2, d2_len), 0);
+}
+
+/* Trailing control record with no data following it.  The sender
+ * closes the connection after the control record; the receiver
+ * drains the control and then observes EOF.
+ */
+TEST_F(tls, data_cmsg_eof)
+{
+	char *data = "payload";
+	char *ctrl = "final";
+	int d_len = strlen(data) + 1;
+	int c_len = strlen(ctrl) + 1;
+	char buf[20];
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+	EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+	EXPECT_EQ(close(self->fd), 0);
+	self->fd = -1;
+
+	/* Consume data */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d_len);
+	EXPECT_EQ(memcmp(buf, data, d_len), 0);
+
+	/* Drain trailing control */
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+				buf, sizeof(buf), 0), c_len);
+	EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+	/* Next recv returns EOF */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), 0);
+}
+
 TEST_F(tls, shutdown)
 {
 	char const *test_str = "test_read";

3/2 2026/07/20 22:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14706
							output: 20
							thoughts: 328
						

Thoughts:
**Analyzing the Socket Record Type Patch**

Okay, so I'm looking at this patch and my initial impression is that it's a pretty clean refactor for how we handle record types at the socket level.  The core idea is to introduce a new callback, `read_sock_rectype`, to the `struct proto_ops`.  This looks like a sensible approach for adding more granular control over how we read data based on record types, which is especially important for protocols like TLS that have complex framing.

The meat of it is that `net/sunrpc/svcsock.c` is being updated to leverage this new callback.  It's replacing some existing functions, `svc_tcp_sock_recvmsg` and `svc_tcp_read_msg`, with the new `svc_tcp_recv_actor` and `svc_tcp_rectype_actor` which seems to suggest a move toward a more actor-based model for handling the TCP read loop. This is interesting, and I’ll want to see how these actors interact in more detail.

The TLS side is also touched, with `net/tls/tls_sw.c` getting a `tls_sw_read_sock_rectype` implementation.  This confirms my initial hypothesis that TLS is a key beneficiary here. This seems logical given the reliance on record framing within TLS. The change is functional, modifying how RPC over TCP handles TLS records.

The definitions of `sk_read_rectype_actor_t` and `read_sock_rectype` in `include/linux/net.h` are standard and expected. I am curious to examine how the `sk_read_rectype_actor_t` interacts with the `read_sock` function.

Now, let's just make sure all the necessary configurations are in place.  We need `CONFIG_SUNRPC` for the RPC side and `CONFIG_TLS` for the TLS implementation. I'll need to check the defconfig more closely. It mentions `CONFIG_TLS_DEVICE` in `net/tls/tls_main.c` but the core changes are in `tls_sw.c`. This also needs consideration.

Okay, let me quickly search the kernel sources to verify how this is used outside of these immediate changes. I want to see how broadly `read_sock_rectype` and `sk_read_rectype_actor_t` are being integrated into the existing networking code. I need to understand the wider implications of this change.



4/2 2026/07/20 22:04 tool grepper
0m Args:
{
  "Expression": "read_sock_rectype"
}

Results:
{
  "Output": "include/linux/net.h=211=struct proto_ops {\n--\ninclude/linux/net.h-274-\t/*\ninclude/linux/net.h:275:\t * read_sock_rectype splits delivery across two callbacks:\ninclude/linux/net.h-276-\t * recv_actor for data records, per the sk_read_actor_t\n--\ninclude/linux/net.h-290-\t */\ninclude/linux/net.h:291:\tint\t\t(*read_sock_rectype)(struct sock *sk,\ninclude/linux/net.h-292-\t\t\t\t\t     read_descriptor_t *desc,\n--\nnet/sunrpc/svcsock.c=958=static int svc_tcp_recv_actor(read_descriptor_t *desc,\n--\nnet/sunrpc/svcsock.c-1039-/*\nnet/sunrpc/svcsock.c:1040: * read_sock_rectype non-data record actor: receives non-data TLS\nnet/sunrpc/svcsock.c-1041- * records (alerts, handshake messages) and translates them into\n--\nnet/sunrpc/svcsock.c=1098=static int svc_tcp_recvfrom(struct svc_rqst *rqstp)\n--\nnet/sunrpc/svcsock.c-1117-\tlock_sock(sk);\nnet/sunrpc/svcsock.c:1118:\tif (svsk-\u003esk_sock-\u003eops-\u003eread_sock_rectype)\nnet/sunrpc/svcsock.c:1119:\t\tlen = svsk-\u003esk_sock-\u003eops-\u003eread_sock_rectype(sk, \u0026desc,\nnet/sunrpc/svcsock.c-1120-\t\t\t\t\t\t\t    svc_tcp_recv_actor,\n--\nnet/tls/tls.h=169=int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\nnet/tls/tls.h-170-\t\t     sk_read_actor_t read_actor);\nnet/tls/tls.h:171:int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,\nnet/tls/tls.h-172-\t\t\t     sk_read_actor_t read_actor,\n--\nnet/tls/tls_main.c=940=static void build_proto_ops(struct proto_ops ops[TLS_NUM_CONFIG][TLS_NUM_CONFIG],\n--\nnet/tls/tls_main.c-951-\tops[TLS_BASE][TLS_SW  ].read_sock\t= tls_sw_read_sock;\nnet/tls/tls_main.c:952:\tops[TLS_BASE][TLS_SW  ].read_sock_rectype\t= tls_sw_read_sock_rectype;\nnet/tls/tls_main.c-953-\n--\nnet/tls/tls_main.c-957-\tops[TLS_SW  ][TLS_SW  ].read_sock\t= tls_sw_read_sock;\nnet/tls/tls_main.c:958:\tops[TLS_SW  ][TLS_SW  ].read_sock_rectype\t= tls_sw_read_sock_rectype;\nnet/tls/tls_main.c-959-\n--\nnet/tls/tls_sw.c=2185=int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n--\nnet/tls/tls_sw.c-2190-\nnet/tls/tls_sw.c:2191:int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,\nnet/tls/tls_sw.c-2192-\t\t\t     sk_read_actor_t read_actor,\n"
}

5/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16553
							output: 22
							thoughts: 147
						

Thoughts:
**Analyzing the Socket Receive Type Implementation**

Okay, so this patch introduces `read_sock_rectype` into the `proto_ops` struct, which is interesting. It looks like it's implemented specifically within `net/tls/tls_sw.c` as `tls_sw_read_sock_rectype`. That makes sense - TLS probably needs to know the record type directly from the socket.

I see it's also being used in `net/sunrpc/svcsock.c` within `svc_tcp_recvfrom`. That's a bit unexpected, but probably means SunRPC can also benefit from this socket-level type retrieval for TCP connections. I'll need to look into how that's being used there.

To get a handle on the changes I need to focus on a few key symbols: `svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `tls_sw_read_sock_rectype` and `__tls_sw_read_sock`.

Given the TLS implementation seems central, I should start by digging into `__tls_sw_read_sock` in `net/tls/tls_sw.c` to see how it's actually pulling this record type information. That will give me a good base understanding of the interaction.



6/2 2026/07/20 22:05 tool grepper
0m Args:
{
  "Expression": "__tls_sw_read_sock"
}

Results:
{
  "Output": "net/tls/tls_sw.c=1983=ssize_t tls_sw_splice_read(struct socket *sock,  loff_t *ppos,\n--\nnet/tls/tls_sw.c-2056-\nnet/tls/tls_sw.c:2057:static int __tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\nnet/tls/tls_sw.c-2058-\t\t\t      sk_read_actor_t read_actor,\n--\nnet/tls/tls_sw.c=2185=int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,\n--\nnet/tls/tls_sw.c-2187-{\nnet/tls/tls_sw.c:2188:\treturn __tls_sw_read_sock(sk, desc, read_actor, NULL);\nnet/tls/tls_sw.c-2189-}\n--\nnet/tls/tls_sw.c=2191=int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,\n--\nnet/tls/tls_sw.c-2194-{\nnet/tls/tls_sw.c:2195:\treturn __tls_sw_read_sock(sk, desc, read_actor, rectype_actor);\nnet/tls/tls_sw.c-2196-}\n"
}

7/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17370
							output: 17
							thoughts: 129
						

Thoughts:
**Analyzing the Impact of Network Configuration Symbols**

Okay, let's break this down. My immediate concern is how these symbols relate: `svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `tls_sw_read_sock_rectype`, and `__tls_sw_read_sock`. These seem to be core to some sort of network protocol handling, probably TCP and likely involving TLS.

The critical question here is the configuration dependency. I need to figure out if enabling specific configurations impacts the functionality. Specifically, I need to check the interplay between `SUNRPC` and `TLS`. Is `SUNRPC` sufficient on its own, or am I going to need something more, perhaps a debug option like `SUNRPC_DEBUG`? Usually, plain `SUNRPC` is enough to enable standard RPC functionality, but I can't assume that without checking. Ah, the plot thickens... `SUNRPC` is a *tristate* config, so I need to be more careful.

Time to dive into `net/sunrpc/Kconfig` and verify the dependencies and implications. I need to understand exactly how `SUNRPC` interacts with these TLS-related symbols. This will determine the scope of the potential impact on the system.



8/2 2026/07/20 22:05 tool grepper
0m Args:
{
  "Expression": "config SUNRPC"
}

Results:
{
  "Output": "net/sunrpc/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nnet/sunrpc/Kconfig:2:config SUNRPC\nnet/sunrpc/Kconfig-3-\ttristate\n--\nnet/sunrpc/Kconfig-5-\nnet/sunrpc/Kconfig:6:config SUNRPC_GSS\nnet/sunrpc/Kconfig-7-\ttristate\n--\nnet/sunrpc/Kconfig-10-\nnet/sunrpc/Kconfig:11:config SUNRPC_BACKCHANNEL\nnet/sunrpc/Kconfig-12-\tbool\n--\nnet/sunrpc/Kconfig-14-\nnet/sunrpc/Kconfig:15:config SUNRPC_SWAP\nnet/sunrpc/Kconfig-16-\tbool\n--\nnet/sunrpc/Kconfig=19=config RPCSEC_GSS_KRB5\n--\nnet/sunrpc/Kconfig-35-\nnet/sunrpc/Kconfig:36:config SUNRPC_DEBUG\nnet/sunrpc/Kconfig-37-\tbool \"RPC: Enable dprintk debugging\"\n--\nnet/sunrpc/Kconfig-49-\nnet/sunrpc/Kconfig:50:config SUNRPC_DEBUG_TRACE\nnet/sunrpc/Kconfig-51-\tbool \"RPC: Send dfprintk() output to the trace buffer\"\n--\nnet/sunrpc/Kconfig-63-\nnet/sunrpc/Kconfig:64:config SUNRPC_XPRT_RDMA\nnet/sunrpc/Kconfig-65-\ttristate \"RPC-over-RDMA transport\"\n"
}

9/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18183
							output: 18
							thoughts: 27
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Checking for RPC-over-TLS Configuration**

Right, so `SUNRPC` is fundamental to NFS and `NFSD` operation. That's the baseline. Now, I'm thinking about security. Given the nature of my work, I should see if there is a config in place for RPC over TLS. It's a key security measure that needs to be checked. I'll need to dig into the configuration files and documentation to confirm that TLS is properly set up for RPC.



10/2 2026/07/20 22:05 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config RPC_TLS"
}

Results:
{
  "error": "no matches"
}

11/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18366
							output: 28
							thoughts: 16
						

Thoughts:
Here's my thought process, summarized as if I were considering the code:

**Checking for TLS Configuration in RPC**

Okay, so I need to see how TLS is configured, or if it's even *considered* in the context of `net/sunrpc`. My first instinct, as always, is to dive right into the core configuration files. Let's see what `net/sunrpc/Kconfig` has to say. That's the go-to place for kernel module configurations, and it's highly likely that any TLS support, or the *potential* for TLS, will be controlled through a Kconfig option. I'm expecting to find flags relating to enabling or disabling TLS, potentially dependent on other configurations (like specific cryptographic libraries), and maybe even options for selecting between different TLS implementations if multiple are supported within this RPC subsystem. I'll need to look for dependencies and relationships between any TLS options I find and other RPC functionalities. Let's get to it.



12/2 2026/07/20 22:05 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "TLS",
  "PathPrefix": "net/sunrpc/Kconfig"
}

Results:
{
  "error": "no matches"
}

13/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18630
							output: 16
							thoughts: 25
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as my own mental process, given my expertise:

**Configuration Considerations**

Alright, so I need to make sure the system is properly configured for both `SUNRPC` and `TLS`. That's the baseline, the fundamental requirements for this setup. Now, let's just clarify… right, `TLS` is actually handled by the configuration option, `config TLS`. Got it. Just double-checking to keep everything straight in my head. No surprises, just the expected baseline.



14/2 2026/07/20 22:05 tool grepper
0m Args:
{
  "Expression": "config TLS"
}

Results:
{
  "Output": "arch/arm/mm/Kconfig=873=config HARDEN_BRANCH_HISTORY\n--\narch/arm/mm/Kconfig-882-\narch/arm/mm/Kconfig:883:config TLS_REG_EMUL\narch/arm/mm/Kconfig-884-\tbool\n--\nnet/tls/Kconfig-4-#\nnet/tls/Kconfig:5:config TLS\nnet/tls/Kconfig-6-\ttristate \"Transport Layer Security support\"\n--\nnet/tls/Kconfig-18-\nnet/tls/Kconfig:19:config TLS_DEVICE\nnet/tls/Kconfig-20-\tbool \"Transport Layer Security HW offload\"\n"
}

15/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18963
							output: 17
							thoughts: 572
						

Thoughts:
**My Analysis of the Patch's Scope and Implications**

Okay, so this patch is definitely dealing with TLS, as suspected. I've confirmed that. Now, let's nail down the specifics. The modifications hit `net/sunrpc/svcsock.c` and `net/tls/tls_sw.c`. The core functions involved seem to be `svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `tls_sw_read_sock_rectype`, and `__tls_sw_read_sock`.

Let me quickly verify if these functions are static or exported as intended. `svc_tcp_recv_actor` is static, good. Same goes for `svc_tcp_rectype_actor`, and `__tls_sw_read_sock`. And yes, `tls_sw_read_sock_rectype` is in `net/tls/tls_sw.c`, confirming it's exported.

Alright, now let's also add `svc_tcp_recvfrom` to my list of focus symbols, since that's been modified as well. This will be important for my analysis.

Now, let's step back and consider the *why*. The patch introduces a `read_sock_rectype` callback within the `proto_ops`, allowing the reading of non-data TLS records directly from the socket. This includes things like alerts and handshake messages. The implementation is in the TLS software path, specifically through `__tls_sw_read_sock` and `tls_sw_read_sock_rectype`. The patch then uses this in the SUNRPC server TCP socket code, with functions `svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, and `svc_tcp_recvfrom`.

This is a functional change, a pretty significant one. It's touching core networking and RPC code. I'm going to have to make sure this is thoroughly fuzzed. Specifically, I'll need to watch for regressions or vulnerabilities introduced in the TLS record parsing and RPC message assembly logic. This is where things could get dicey.

Regarding configuration, I've got `TLS` confirmed. SUNRPC is also present. Ah, right, `SUNRPC` is tristate, selected by `NFS_FS` and `NFSD`. Direct configuration isn't available. Enabling it implicitly requires enabling either of those. I'll need to remember that. It's likely that `NFSD` is the more relevant one for my analysis. I'll take a quick peek at `fs/nfsd/Kconfig` to confirm any dependencies I might need to consider.

So, to summarize my focused symbols are:
- `svc_tcp_recv_actor`
- `svc_tcp_rectype_actor`
- `svc_tcp_recvfrom`
- `tls_sw_read_sock_rectype`
- `__tls_sw_read_sock`



16/2 2026/07/20 22:05 tool grepper
0m Args:
{
  "Expression": "config NFSD"
}

Results:
{
  "Output": "fs/nfsd/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/nfsd/Kconfig:2:config NFSD\nfs/nfsd/Kconfig-3-\ttristate \"NFS server support\"\n--\nfs/nfsd/Kconfig-39-\nfs/nfsd/Kconfig:40:config NFSD_V2\nfs/nfsd/Kconfig-41-\tbool \"NFS server support for NFS version 2 (DEPRECATED)\"\n--\nfs/nfsd/Kconfig-50-\nfs/nfsd/Kconfig:51:config NFSD_V2_ACL\nfs/nfsd/Kconfig-52-\tbool \"NFS server support for the NFSv2 ACL protocol extension\"\n--\nfs/nfsd/Kconfig-54-\nfs/nfsd/Kconfig:55:config NFSD_V3_ACL\nfs/nfsd/Kconfig-56-\tbool \"NFS server support for the NFSv3 ACL protocol extension\"\n--\nfs/nfsd/Kconfig-76-\nfs/nfsd/Kconfig:77:config NFSD_V4\nfs/nfsd/Kconfig-78-\tbool \"NFS server support for NFS version 4\"\n--\nfs/nfsd/Kconfig-93-\nfs/nfsd/Kconfig:94:config NFSD_PNFS\nfs/nfsd/Kconfig-95-\tbool\nfs/nfsd/Kconfig-96-\nfs/nfsd/Kconfig:97:config NFSD_BLOCKLAYOUT\nfs/nfsd/Kconfig-98-\tbool \"NFSv4.1 server support for pNFS block layouts\"\n--\nfs/nfsd/Kconfig-109-\nfs/nfsd/Kconfig:110:config NFSD_SCSILAYOUT\nfs/nfsd/Kconfig-111-\tbool \"NFSv4.1 server support for pNFS SCSI layouts\"\n--\nfs/nfsd/Kconfig-123-\nfs/nfsd/Kconfig:124:config NFSD_FLEXFILELAYOUT\nfs/nfsd/Kconfig-125-\tbool \"NFSv4.1 server support for pNFS Flex File layouts\"\n--\nfs/nfsd/Kconfig-140-\nfs/nfsd/Kconfig:141:config NFSD_V4_2_INTER_SSC\nfs/nfsd/Kconfig-142-\tbool \"NFSv4.2 inter server to server COPY\"\n--\nfs/nfsd/Kconfig-150-\nfs/nfsd/Kconfig:151:config NFSD_V4_SECURITY_LABEL\nfs/nfsd/Kconfig-152-\tbool \"Provide Security Label support for NFSv4 server\"\n--\nfs/nfsd/Kconfig-163-\nfs/nfsd/Kconfig:164:config NFSD_LEGACY_CLIENT_TRACKING\nfs/nfsd/Kconfig-165-\tbool \"Support legacy NFSv4 client tracking methods (DEPRECATED)\"\n--\nfs/nfsd/Kconfig-179-\nfs/nfsd/Kconfig:180:config NFSD_V4_POSIX_ACLS\nfs/nfsd/Kconfig-181-\tbool \"Support NFSv4 POSIX draft ACLs\"\n"
}

17/2 2026/07/20 22:05 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21048
							output: 233
							thoughts: 285
						

Thoughts:
**My Configuration Strategy for Enhanced Security**

Okay, so I need to make sure this new functionality, this `read_sock_rectype` callback for non-data TLS records, is properly integrated and tested. My immediate thought is to add `NFSD` to the configurations, since that's a key component, and then of course I need `TLS` enabled. That seems like a given, but let's just make sure.

I'm going to double-check `net/tls/Kconfig` to confirm the `config TLS` prompt is present. Yes, it is. Excellent. Now, putting it all together, I have my enablement config list which is: `["NFSD", "TLS"]`.

I also have identified these very relevant symbols which I should focus my fuzzing efforts on: `["svc_tcp_recv_actor", "svc_tcp_rectype_actor", "svc_tcp_recvfrom", "tls_sw_read_sock_rectype", "__tls_sw_read_sock"]`. These directly relate to the new `read_sock_rectype` implementation within the TLS software path and the integration into the SUNRPC server TCP socket code. The `read_sock_rectype` callback allows direct reading of non-data TLS records directly from the socket and this will have implications on core networking and RPC code.

Given the functional nature of these changes, introducing a new proto_ops callback, I believe that this absolutely warrants fuzzing. The goal here is to catch any regressions or vulnerabilities that might be introduced in the TLS record parsing and RPC message assembly logic. This is definitely worth pursuing.

I'm ready to commit to fuzzing this. Let's call the set-results function and get the ball rolling.



18/2 2026/07/20 22:05 tool set-results
0m Args:
{
  "EnableConfigs": [
    "NFSD",
    "TLS"
  ],
  "FocusSymbols": [
    "svc_tcp_recv_actor",
    "svc_tcp_rectype_actor",
    "svc_tcp_recvfrom",
    "tls_sw_read_sock_rectype",
    "__tls_sw_read_sock"
  ],
  "Reasoning": "The patch introduces a new proto_ops callback `read_sock_rectype` to allow reading non-data TLS records (like alerts and handshake messages) directly from the socket. It implements this in the TLS software path (`__tls_sw_read_sock`, `tls_sw_read_sock_rectype`) and uses it in the SUNRPC server TCP socket code (`svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `svc_tcp_recvfrom`). This is a functional change in core networking and RPC code that should be fuzzed to ensure no regressions or vulnerabilities are introduced in the TLS record parsing and RPC message assembly logic.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "NFSD",
    "TLS"
  ],
  "FocusSymbols": [
    "svc_tcp_recv_actor",
    "svc_tcp_rectype_actor",
    "svc_tcp_recvfrom",
    "tls_sw_read_sock_rectype",
    "__tls_sw_read_sock"
  ],
  "Reasoning": "The patch introduces a new proto_ops callback `read_sock_rectype` to allow reading non-data TLS records (like alerts and handshake messages) directly from the socket. It implements this in the TLS software path (`__tls_sw_read_sock`, `tls_sw_read_sock_rectype`) and uses it in the SUNRPC server TCP socket code (`svc_tcp_recv_actor`, `svc_tcp_rectype_actor`, `svc_tcp_recvfrom`). This is a functional change in core networking and RPC code that should be fuzzed to ensure no regressions or vulnerabilities are introduced in the TLS record parsing and RPC message assembly logic.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)