AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/10 14:33 flow patch-triage
4m Args:
null

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VIRTIO_VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "BPF_SYSCALL",
    "VIRTIO"
  ],
  "FocusSymbols": [
    "virtio_transport_recv_pkt_batch",
    "virtio_transport_rx_batch_finish",
    "virtio_transport_recv_pkt",
    "vsock_rx_callback_handoff",
    "vsock_rx_cb_is_native",
    "vsock_bpf_update_proto"
  ],
  "KMSANReasoning": "The patch series implements receive packet batching and deferred callback handling for virtio-vsock to optimize locking overhead across sequences of STREAM/RW packets.\n\nEvaluation of KMSAN vs KASAN/LOCKDEP applicability:\n1. Memory initialization: The new stack-allocated batch tracking structure (`struct virtio_transport_rx_batch`) is zero-initialized (`batch = {}`). New socket fields in `struct vsock_sock` (`default_data_ready`, `default_write_space`, `rx_cb_work`, `rx_cb_retried`) are allocated via `sk_alloc` (which zeroes memory) and explicitly initialized in `__vsock_create`. Address structures (`src`, `dst`) are initialized via `vsock_addr_init`, which zeroes the entire `struct sockaddr_vm` via `memset`. Context structures and boolean flags are fully assigned before being read or used in conditional branches.\n2. Info-leaks / User-space copies: The changes do not introduce or modify any data transfers to user space (`copy_to_user`, ioctl, netlink, or socket options).\n3. Buffer bounds / uninitialized buffer reads: The batching logic does not alter buffer slicing or payload boundary checks in a way that could expose uninitialized memory.\n4. Bug profile: The primary failure modes of this patch involve socket reference counting, socket/callback lock ordering, race conditions with BPF/sockmap callback changes, or use-after-free conditions on socket or sk_buff lifetimes. These are comprehensively addressed by KASAN, LOCKDEP, and standard kernel debugging facilities, not KMSAN.\n\nTherefore, running a dedicated KMSAN session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces an RX packet batching mechanism for virtio vsock, allowing multiple packets to be processed under a single socket lock and deferring data_ready and write_space callbacks. It also adds callback handoff handling via delayed work when custom callbacks or BPF sockmap protocol replacements are active. These changes alter socket locking, state machine transitions, and callback synchronization across AF_VSOCK and virtio transports, which are fully reachable in standard virtualized environments (e.g., QEMU virtio-vsock and vsock loopback). Therefore, fuzzing is warranted to test for potential regressions, concurrency races, or state mismatches.",
  "WorthFuzzing": true
}

1/1 2026/10/10 14:33 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 976e9947786434b4e7066947337110fe5ba02405\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Oct 10 14:33:15 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h\nindex f91704731057e..21a3460b940f2 100644\n--- a/include/linux/virtio_vsock.h\n+++ b/include/linux/virtio_vsock.h\n@@ -282,6 +282,22 @@ void virtio_transport_destruct(struct vsock_sock *vsk);\n \n void virtio_transport_recv_pkt(struct virtio_transport *t,\n \t\t\t       struct sk_buff *skb, struct net *net);\n+\n+struct virtio_transport_rx_batch {\n+\tstruct sock *sk;\n+\tunsigned int pkts;\n+\tsize_t bytes;\n+\tstruct net *net;\n+\tstruct sockaddr_vm src;\n+\tstruct sockaddr_vm dst;\n+\tbool write_space_pending;\n+\tbool data_ready_pending;\n+};\n+\n+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,\n+\t\t\t\t     struct sk_buff *skb, struct net *net,\n+\t\t\t\t     struct virtio_transport_rx_batch *batch);\n+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch);\n void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);\n u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);\n void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);\ndiff --git a/include/net/af_vsock.h b/include/net/af_vsock.h\nindex 5549298c1ec6b..86d6e6ba3d9df 100644\n--- a/include/net/af_vsock.h\n+++ b/include/net/af_vsock.h\n@@ -63,6 +63,11 @@ struct vsock_sock {\n \tu32 peer_shutdown;\n \tbool sent_request;\n \tbool ignore_connecting_rst;\n+\t/* Initial callbacks, used to identify replacements. */\n+\tvoid (*default_data_ready)(struct sock *sk);\n+\tvoid (*default_write_space)(struct sock *sk);\n+\tstruct delayed_work rx_cb_work;\n+\tbool rx_cb_retried;\n \n \t/* Protected by lock_sock(sk) */\n \tu64 buffer_size;\n@@ -78,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk);\n s64 vsock_stream_has_space(struct vsock_sock *vsk);\n struct sock *vsock_create_connected(struct sock *parent);\n void vsock_data_ready(struct sock *sk);\n+bool vsock_rx_cb_is_native(struct sock *sk);\n+void vsock_rx_callback_kick(struct sock *sk);\n+void vsock_rx_callback_handoff(struct sock *sk);\n \n /**** TRANSPORT ****/\n \ndiff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c\nindex 44cee74519555..df6faee0bd37a 100644\n--- a/net/vmw_vsock/af_vsock.c\n+++ b/net/vmw_vsock/af_vsock.c\n@@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)\n \treturn retval;\n }\n \n+#define VSOCK_RX_CB_HANDOFF_MAX 64\n+\n+static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)\n+{\n+\tstruct vsock_sock *vsk = vsock_sk(sk);\n+\n+\tsock_hold(sk);\n+\tif (!schedule_delayed_work(\u0026vsk-\u003erx_cb_work, delay))\n+\t\tsock_put(sk);\n+}\n+\n+void vsock_rx_callback_kick(struct sock *sk)\n+{\n+\tvsock_rx_callback_queue(sk, 0);\n+}\n+\n+/* Caller holds sk_callback_lock. */\n+bool vsock_rx_cb_is_native(struct sock *sk)\n+{\n+\tstruct vsock_sock *vsk = vsock_sk(sk);\n+\n+\treturn READ_ONCE(sk-\u003esk_prot) == sk-\u003esk_prot_creator \u0026\u0026\n+\t       READ_ONCE(sk-\u003esk_data_ready) == vsk-\u003edefault_data_ready \u0026\u0026\n+\t       READ_ONCE(sk-\u003esk_write_space) == vsk-\u003edefault_write_space;\n+}\n+EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);\n+\n+/* Caller holds lock_sock(). */\n+void vsock_rx_callback_handoff(struct sock *sk)\n+{\n+\tstruct vsock_sock *vsk = vsock_sk(sk);\n+\tvoid (*write_space)(struct sock *sk);\n+\tvoid (*data_ready)(struct sock *sk);\n+\tbool wrote = false;\n+\ts64 before, after;\n+\tint i;\n+\n+\tif (sock_flag(sk, SOCK_DEAD) || !vsk-\u003etransport)\n+\t\treturn;\n+\n+\tread_lock_bh(\u0026sk-\u003esk_callback_lock);\n+\tdata_ready = READ_ONCE(sk-\u003esk_data_ready);\n+\tif (READ_ONCE(sk-\u003esk_prot) != sk-\u003esk_prot_creator \u0026\u0026\n+\t    data_ready == vsk-\u003edefault_data_ready) {\n+\t\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n+\t\t/*\n+\t\t * start_verdict publishes sk_data_ready after the proto swap.\n+\t\t * Retry once before treating this as a stable non-RX setup.\n+\t\t */\n+\t\tif (!vsk-\u003erx_cb_retried) {\n+\t\t\tvsk-\u003erx_cb_retried = true;\n+\t\t\tvsock_rx_callback_queue(sk, 1);\n+\t\t\treturn;\n+\t\t}\n+\t\tvsk-\u003erx_cb_retried = false;\n+\t\tdata_ready(sk);\n+\t\treturn;\n+\t}\n+\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n+\tvsk-\u003erx_cb_retried = false;\n+\n+\tfor (i = 0; i \u003c VSOCK_RX_CB_HANDOFF_MAX; i++) {\n+\t\tread_lock_bh(\u0026sk-\u003esk_callback_lock);\n+\t\tdata_ready = READ_ONCE(sk-\u003esk_data_ready);\n+\t\twrite_space = READ_ONCE(sk-\u003esk_write_space);\n+\t\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n+\n+\t\tif (!wrote \u0026\u0026 write_space != vsk-\u003edefault_write_space) {\n+\t\t\twrite_space(sk);\n+\t\t\twrote = true;\n+\t\t}\n+\t\tif (data_ready == vsk-\u003edefault_data_ready) {\n+\t\t\tdata_ready(sk);\n+\t\t\tbreak;\n+\t\t}\n+\t\tbefore = vsock_stream_has_data(vsk);\n+\t\tif (before \u003c= 0)\n+\t\t\tbreak;\n+\t\tdata_ready(sk);\n+\t\tafter = vsock_stream_has_data(vsk);\n+\t\tif (after \u003e= before)\n+\t\t\tbreak;\n+\t\tif (i == VSOCK_RX_CB_HANDOFF_MAX - 1 \u0026\u0026 after \u003e 0)\n+\t\t\tvsock_rx_callback_queue(sk, 0);\n+\t}\n+}\n+EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff);\n+\n+static void vsock_rx_callback_work(struct work_struct *work)\n+{\n+\tstruct vsock_sock *vsk = container_of(work, struct vsock_sock,\n+\t\t\t\t\t      rx_cb_work.work);\n+\tstruct sock *sk = sk_vsock(vsk);\n+\n+\tlock_sock(sk);\n+\tif (!sock_flag(sk, SOCK_DEAD))\n+\t\tvsock_rx_callback_handoff(sk);\n+\trelease_sock(sk);\n+\tsock_put(sk);\n+}\n+\n static void vsock_connect_timeout(struct work_struct *work);\n \n static struct sock *__vsock_create(struct net *net,\n@@ -958,6 +1059,8 @@ static struct sock *__vsock_create(struct net *net,\n \t\tsk-\u003esk_type = type;\n \n \tvsk = vsock_sk(sk);\n+\tvsk-\u003edefault_data_ready = sk-\u003esk_data_ready;\n+\tvsk-\u003edefault_write_space = sk-\u003esk_write_space;\n \tvsock_addr_init(\u0026vsk-\u003elocal_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n \tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n \n@@ -975,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net,\n \tWRITE_ONCE(vsk-\u003epeer_shutdown, 0);\n \tINIT_DELAYED_WORK(\u0026vsk-\u003econnect_work, vsock_connect_timeout);\n \tINIT_DELAYED_WORK(\u0026vsk-\u003epending_work, vsock_pending_work);\n+\tINIT_DELAYED_WORK(\u0026vsk-\u003erx_cb_work, vsock_rx_callback_work);\n \n \tpsk = parent ? vsock_sk(parent) : NULL;\n \tif (parent) {\ndiff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c\nindex 4f9aa9c4c3aa5..ef076732158d4 100644\n--- a/net/vmw_vsock/virtio_transport.c\n+++ b/net/vmw_vsock/virtio_transport.c\n@@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)\n \treturn seqpacket_allow;\n }\n \n+/*\n+ * Keep a bounded run of packets for one socket under a single socket lock.\n+ * Limit packet count and payload size to bound the work done while locked.\n+ */\n+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS\t64\n+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES\t(64 * 1024)\n+\n static void virtio_transport_rx_work(struct work_struct *work)\n {\n+\tstruct virtio_transport_rx_batch batch = {};\n \tstruct virtio_vsock *vsock =\n \t\tcontainer_of(work, struct virtio_vsock, rx_work);\n \tstruct virtqueue *vq;\n@@ -666,6 +674,7 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \t\t\t/* Drop short/long packets */\n \t\t\tif (unlikely(len \u003c sizeof(*hdr) ||\n \t\t\t\t     len \u003e virtio_vsock_skb_len(skb))) {\n+\t\t\t\tvirtio_transport_rx_batch_finish(\u0026batch);\n \t\t\t\tkfree_skb(skb);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -673,6 +682,7 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \t\t\thdr = virtio_vsock_hdr(skb);\n \t\t\tpayload_len = le32_to_cpu(hdr-\u003elen);\n \t\t\tif (unlikely(payload_len \u003e len - sizeof(*hdr))) {\n+\t\t\t\tvirtio_transport_rx_batch_finish(\u0026batch);\n \t\t\t\tkfree_skb(skb);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -680,16 +690,33 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \t\t\tif (payload_len)\n \t\t\t\tvirtio_vsock_skb_put(skb, payload_len);\n \n+\t\t\tif (batch.sk \u0026\u0026\n+\t\t\t    payload_len \u003e VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES -\n+\t\t\t\t\t  batch.bytes) {\n+\t\t\t\tvirtio_transport_rx_batch_finish(\u0026batch);\n+\t\t\t}\n+\n \t\t\tvirtio_transport_deliver_tap_pkt(skb);\n \n \t\t\t/* Force virtio-transport into global mode since it\n \t\t\t * does not yet support local-mode namespacing.\n \t\t\t */\n-\t\t\tvirtio_transport_recv_pkt(\u0026virtio_transport, skb, NULL);\n+\t\t\tvirtio_transport_recv_pkt_batch(\u0026virtio_transport, skb, NULL,\n+\t\t\t\t\t\t\t\u0026batch);\n+\n+\t\t\tif (batch.sk) {\n+\t\t\t\tbatch.pkts++;\n+\t\t\t\tbatch.bytes += payload_len;\n+\t\t\t\tif (batch.pkts \u003e= VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS ||\n+\t\t\t\t    batch.bytes \u003e= VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES) {\n+\t\t\t\t\tvirtio_transport_rx_batch_finish(\u0026batch);\n+\t\t\t\t}\n+\t\t\t}\n \t\t}\n \t} while (!virtqueue_enable_cb(vq));\n \n out:\n+\tvirtio_transport_rx_batch_finish(\u0026batch);\n \tif (vsock-\u003erx_buf_nr \u003c vsock-\u003erx_buf_max_nr / 2)\n \t\tvirtio_vsock_rx_fill(vsock);\n out_nofill:\ndiff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c\nindex f225f53ed4bab..6b96d8ac0d598 100644\n--- a/net/vmw_vsock/virtio_transport_common.c\n+++ b/net/vmw_vsock/virtio_transport_common.c\n@@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,\n \tskb_queue_splice(\u0026new_queue, \u0026vvs-\u003erx_queue);\n }\n \n+static bool\n+virtio_transport_rx_skb_has_headroom(struct virtio_vsock_sock *vvs)\n+{\n+\treturn (u64)(skb_queue_len(\u0026vvs-\u003erx_queue) + 1) * SKB_TRUESIZE(0) \u003c=\n+\t\tvvs-\u003ebuf_alloc;\n+}\n+\n static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,\n \t\t\t\t\tu32 len)\n {\n-\tu64 skb_overhead = (skb_queue_len(\u0026vvs-\u003erx_queue) + 1) * SKB_TRUESIZE(0);\n \n \t/* Allow at most buf_alloc * 2 total budget (payload + overhead),\n \t * similar to how SO_RCVBUF is doubled to reserve space for sk_buff\n@@ -588,7 +594,7 @@ static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,\n \t * queue growth.\n \t */\n \tif ((u64)vvs-\u003ebuf_used + len \u003e vvs-\u003ebuf_alloc ||\n-\t    skb_overhead \u003e vvs-\u003ebuf_alloc)\n+\t    !virtio_transport_rx_skb_has_headroom(vvs))\n \t\treturn false;\n \n \tvvs-\u003erx_bytes += len;\n@@ -1583,10 +1589,12 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,\n \n static int\n virtio_transport_recv_connected(struct sock *sk,\n-\t\t\t\tstruct sk_buff *skb)\n+\t\t\t\tstruct sk_buff *skb,\n+\t\t\t\tbool *data_ready_pending)\n {\n \tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n \tstruct vsock_sock *vsk = vsock_sk(sk);\n+\tvoid (*data_ready)(struct sock *sk);\n \tint err = 0;\n \n \tswitch (le16_to_cpu(hdr-\u003eop)) {\n@@ -1602,7 +1610,23 @@ virtio_transport_recv_connected(struct sock *sk,\n \t\t\tvsock_remove_sock(vsk);\n \t\t\tbreak;\n \t\t}\n-\t\tvsock_data_ready(sk);\n+\t\tif (!data_ready_pending) {\n+\t\t\tvsock_data_ready(sk);\n+\t\t} else {\n+\t\t\tdata_ready = READ_ONCE(sk-\u003esk_data_ready);\n+\t\t\tif (data_ready == vsk-\u003edefault_data_ready) {\n+\t\t\t\tif (!*data_ready_pending \u0026\u0026\n+\t\t\t\t    (vsock_stream_has_data(vsk) \u003e= sk-\u003esk_rcvlowat ||\n+\t\t\t\t     sock_flag(sk, SOCK_DONE)))\n+\t\t\t\t\t*data_ready_pending = true;\n+\t\t\t} else {\n+\t\t\t\t/* Use the callback seen for this packet. */\n+\t\t\t\t*data_ready_pending = false;\n+\t\t\t\tif (vsock_stream_has_data(vsk) \u003e= sk-\u003esk_rcvlowat ||\n+\t\t\t\t    sock_flag(sk, SOCK_DONE))\n+\t\t\t\t\tdata_ready(sk);\n+\t\t\t}\n+\t\t}\n \t\treturn err;\n \tcase VIRTIO_VSOCK_OP_CREDIT_REQUEST:\n \t\tvirtio_transport_send_credit_update(vsk);\n@@ -1774,84 +1798,133 @@ static bool virtio_transport_valid_type(u16 type)\n \t       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);\n }\n \n-/* We are under the virtio-vsock's vsock-\u003erx_lock or vhost-vsock's vq-\u003emutex\n- * lock.\n- */\n-void virtio_transport_recv_pkt(struct virtio_transport *t,\n-\t\t\t       struct sk_buff *skb, struct net *net)\n+static void\n+virtio_transport_recv_pkt_init_addrs(struct sk_buff *skb,\n+\t\t\t\t     struct sockaddr_vm *src,\n+\t\t\t\t     struct sockaddr_vm *dst)\n {\n \tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n-\tstruct sockaddr_vm src, dst;\n-\tstruct vsock_sock *vsk;\n-\tstruct sock *sk;\n-\tbool space_available;\n \n-\tvsock_addr_init(\u0026src, le64_to_cpu(hdr-\u003esrc_cid),\n+\tvsock_addr_init(src, le64_to_cpu(hdr-\u003esrc_cid),\n \t\t\tle32_to_cpu(hdr-\u003esrc_port));\n-\tvsock_addr_init(\u0026dst, le64_to_cpu(hdr-\u003edst_cid),\n+\tvsock_addr_init(dst, le64_to_cpu(hdr-\u003edst_cid),\n \t\t\tle32_to_cpu(hdr-\u003edst_port));\n+}\n \n-\ttrace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,\n-\t\t\t\t\tdst.svm_cid, dst.svm_port,\n+static void\n+virtio_transport_trace_recv_pkt(struct sk_buff *skb,\n+\t\t\t\tconst struct sockaddr_vm *src,\n+\t\t\t\tconst struct sockaddr_vm *dst)\n+{\n+\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n+\n+\ttrace_virtio_transport_recv_pkt(src-\u003esvm_cid, src-\u003esvm_port,\n+\t\t\t\t\tdst-\u003esvm_cid, dst-\u003esvm_port,\n \t\t\t\t\tle32_to_cpu(hdr-\u003elen),\n \t\t\t\t\tle16_to_cpu(hdr-\u003etype),\n \t\t\t\t\tle16_to_cpu(hdr-\u003eop),\n \t\t\t\t\tle32_to_cpu(hdr-\u003eflags),\n \t\t\t\t\tle32_to_cpu(hdr-\u003ebuf_alloc),\n \t\t\t\t\tle32_to_cpu(hdr-\u003efwd_cnt));\n+}\n \n-\tif (!virtio_transport_valid_type(le16_to_cpu(hdr-\u003etype))) {\n-\t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n-\t\tgoto free_pkt;\n-\t}\n+static struct sock *\n+virtio_transport_recv_pkt_find_socket(struct sk_buff *skb,\n+\t\t\t\t      struct sockaddr_vm *src,\n+\t\t\t\t      struct sockaddr_vm *dst,\n+\t\t\t\t      struct net *net)\n+{\n+\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n+\tstruct sock *sk;\n \n-\t/* The socket must be in connected or bound table\n-\t * otherwise send reset back\n-\t */\n-\tsk = vsock_find_connected_socket_net(\u0026src, \u0026dst, net);\n-\tif (!sk) {\n-\t\tsk = vsock_find_bound_socket_net(\u0026dst, net);\n-\t\tif (!sk) {\n-\t\t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n-\t\t\tgoto free_pkt;\n-\t\t}\n-\t}\n+\tif (!virtio_transport_valid_type(le16_to_cpu(hdr-\u003etype)))\n+\t\treturn NULL;\n+\n+\tsk = vsock_find_connected_socket_net(src, dst, net);\n+\tif (!sk)\n+\t\tsk = vsock_find_bound_socket_net(dst, net);\n+\tif (!sk)\n+\t\treturn NULL;\n \n \tif (virtio_transport_get_type(sk) != le16_to_cpu(hdr-\u003etype)) {\n-\t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n \t\tsock_put(sk);\n-\t\tgoto free_pkt;\n+\t\treturn NULL;\n \t}\n \n-\tif (!skb_set_owner_sk_safe(skb, sk)) {\n-\t\tWARN_ONCE(1, \"receiving vsock socket has sk_refcnt == 0\\n\");\n-\t\tgoto free_pkt;\n-\t}\n+\treturn sk;\n+}\n \n-\tvsk = vsock_sk(sk);\n+struct virtio_transport_rx_pkt_ctx {\n+\tstruct net *net;\n+\tconst struct sockaddr_vm *src;\n+\tconst struct sockaddr_vm *dst;\n+\tbool *batchable;\n+\tstruct virtio_transport_rx_batch *batch;\n+\tbool defer_data_ready;\n+};\n \n-\tlock_sock(sk);\n+static bool\n+virtio_transport_recv_pkt_batchable(struct virtio_transport *t,\n+\t\t\t\t    struct sock *sk)\n+{\n+\tstruct vsock_sock *vsk = vsock_sk(sk);\n+\tstruct virtio_vsock_sock *vvs = vsk-\u003etrans;\n+\n+\treturn sk-\u003esk_state == TCP_ESTABLISHED \u0026\u0026\n+\t       sk-\u003esk_type == SOCK_STREAM \u0026\u0026\n+\t       READ_ONCE(sk-\u003esk_prot) == sk-\u003esk_prot_creator \u0026\u0026\n+\t       !sock_flag(sk, SOCK_DONE) \u0026\u0026\n+\t       vsk-\u003etransport == \u0026t-\u003etransport \u0026\u0026\n+\t       vvs \u0026\u0026 virtio_transport_rx_skb_has_headroom(vvs);\n+}\n+\n+/*\n+ * The caller holds sk's socket lock.  Set @batchable if the socket can remain\n+ * locked for another ordinary STREAM/RW packet.  Return true if the caller\n+ * must free @skb.\n+ */\n+static bool\n+virtio_transport_recv_pkt_locked(struct virtio_transport *t,\n+\t\t\t\t struct sk_buff *skb, struct sock *sk,\n+\t\t\t\t const struct virtio_transport_rx_pkt_ctx *ctx)\n+{\n+\tconst struct sockaddr_vm *src = ctx-\u003esrc;\n+\tconst struct sockaddr_vm *dst = ctx-\u003edst;\n+\tstruct vsock_sock *vsk = vsock_sk(sk);\n+\tvoid (*write_space)(struct sock *sk);\n+\tstruct net *net = ctx-\u003enet;\n+\tbool space_available;\n \n-\t/* Check if sk has been closed or assigned to another transport before\n-\t * lock_sock (note: listener sockets are not assigned to any transport)\n+\tif (ctx-\u003ebatchable)\n+\t\t*ctx-\u003ebatchable = false;\n+\n+\t/* Check after acquiring the socket lock. Listener sockets accept packets\n+\t * from any source and are not assigned to a transport.\n \t */\n \tif (sock_flag(sk, SOCK_DONE) ||\n \t    (sk-\u003esk_state != TCP_LISTEN \u0026\u0026\n-\t     !vsock_check_source(vsk, \u0026t-\u003etransport, \u0026src))) {\n+\t     !vsock_check_source(vsk, \u0026t-\u003etransport, src))) {\n \t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n-\t\trelease_sock(sk);\n-\t\tsock_put(sk);\n-\t\tgoto free_pkt;\n+\t\treturn true;\n \t}\n \n \tspace_available = virtio_transport_space_update(sk, skb);\n \n \t/* Update CID in case it has changed after a transport reset event */\n \tif (vsk-\u003elocal_addr.svm_cid != VMADDR_CID_ANY)\n-\t\tvsk-\u003elocal_addr.svm_cid = dst.svm_cid;\n-\n-\tif (space_available)\n-\t\tsk-\u003esk_write_space(sk);\n+\t\tvsk-\u003elocal_addr.svm_cid = dst-\u003esvm_cid;\n+\n+\tif (space_available) {\n+\t\twrite_space = READ_ONCE(sk-\u003esk_write_space);\n+\t\tif (ctx-\u003ebatch \u0026\u0026\n+\t\t    write_space == vsk-\u003edefault_write_space \u0026\u0026\n+\t\t    virtio_transport_recv_pkt_batchable(t, sk)) {\n+\t\t\tctx-\u003ebatch-\u003ewrite_space_pending = true;\n+\t\t} else {\n+\t\t\t/* Use the callback seen for this packet. */\n+\t\t\twrite_space(sk);\n+\t\t}\n+\t}\n \n \tswitch (sk-\u003esk_state) {\n \tcase TCP_LISTEN:\n@@ -1863,7 +1936,9 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,\n \t\tkfree_skb(skb);\n \t\tbreak;\n \tcase TCP_ESTABLISHED:\n-\t\tvirtio_transport_recv_connected(sk, skb);\n+\t\tvirtio_transport_recv_connected(sk, skb,\n+\t\t\t\t\t\tctx-\u003edefer_data_ready \u0026\u0026 ctx-\u003ebatch ?\n+\t\t\t\t\t\t\u0026ctx-\u003ebatch-\u003edata_ready_pending : NULL);\n \t\tbreak;\n \tcase TCP_CLOSING:\n \t\tvirtio_transport_recv_disconnecting(sk, skb);\n@@ -1875,12 +1950,52 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,\n \t\tbreak;\n \t}\n \n+\tif (ctx-\u003ebatchable)\n+\t\t*ctx-\u003ebatchable = virtio_transport_recv_pkt_batchable(t, sk);\n+\n+\treturn false;\n+}\n+\n+/* We are under the virtio-vsock's vsock-\u003erx_lock or vhost-vsock's vq-\u003emutex\n+ * lock.\n+ */\n+void virtio_transport_recv_pkt(struct virtio_transport *t,\n+\t\t\t       struct sk_buff *skb, struct net *net)\n+{\n+\tstruct virtio_transport_rx_pkt_ctx ctx;\n+\tstruct sockaddr_vm src, dst;\n+\tstruct sock *sk;\n+\tbool free_pkt;\n+\n+\tvirtio_transport_recv_pkt_init_addrs(skb, \u0026src, \u0026dst);\n+\tvirtio_transport_trace_recv_pkt(skb, \u0026src, \u0026dst);\n+\n+\tsk = virtio_transport_recv_pkt_find_socket(skb, \u0026src, \u0026dst, net);\n+\tif (!sk) {\n+\t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n+\t\tgoto free_pkt;\n+\t}\n+\n+\tif (!skb_set_owner_sk_safe(skb, sk)) {\n+\t\tWARN_ONCE(1, \"receiving vsock socket has sk_refcnt == 0\\n\");\n+\t\tgoto free_pkt;\n+\t}\n+\n+\tlock_sock(sk);\n+\tctx = (struct virtio_transport_rx_pkt_ctx) {\n+\t\t.net = net,\n+\t\t.src = \u0026src,\n+\t\t.dst = \u0026dst,\n+\t};\n+\tfree_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, \u0026ctx);\n \trelease_sock(sk);\n \n \t/* Release refcnt obtained when we fetched this socket out of the\n \t * bound or connected list.\n \t */\n \tsock_put(sk);\n+\tif (free_pkt)\n+\t\tkfree_skb(skb);\n \treturn;\n \n free_pkt:\n@@ -1888,6 +2003,178 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,\n }\n EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);\n \n+/*\n+ * Finish the RX batch.\n+ * For a non-empty batch, the caller must hold the socket lock acquired with\n+ * lock_sock(). This function releases the lock and the batch's lookup\n+ * reference. An empty batch is a no-op.\n+ */\n+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)\n+{\n+\tbool write_space_pending = batch-\u003ewrite_space_pending;\n+\tbool data_ready_pending = batch-\u003edata_ready_pending;\n+\tstruct sock *sk = batch-\u003esk;\n+\tstruct vsock_sock *vsk;\n+\tbool native;\n+\n+\tbatch-\u003esk = NULL;\n+\tbatch-\u003epkts = 0;\n+\tbatch-\u003ebytes = 0;\n+\tbatch-\u003enet = NULL;\n+\tbatch-\u003ewrite_space_pending = false;\n+\tbatch-\u003edata_ready_pending = false;\n+\n+\tif (!sk)\n+\t\treturn;\n+\n+\tvsk = vsock_sk(sk);\n+\tif (write_space_pending)\n+\t\tvsk-\u003edefault_write_space(sk);\n+\n+\tread_lock_bh(\u0026sk-\u003esk_callback_lock);\n+\tnative = vsock_rx_cb_is_native(sk);\n+\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n+\n+\tif (native) {\n+\t\trelease_sock(sk);\n+\t\tif (data_ready_pending)\n+\t\t\tvsk-\u003edefault_data_ready(sk);\n+\t\tsock_put(sk);\n+\t\treturn;\n+\t}\n+\n+\tif (write_space_pending || data_ready_pending)\n+\t\tvsock_rx_callback_handoff(sk);\n+\n+\trelease_sock(sk);\n+\tsock_put(sk);\n+}\n+EXPORT_SYMBOL_GPL(virtio_transport_rx_batch_finish);\n+\n+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,\n+\t\t\t\t     struct sk_buff *skb, struct net *net,\n+\t\t\t\t     struct virtio_transport_rx_batch *batch)\n+{\n+\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n+\tbool batchable, defer_data_ready, start_batch;\n+\tstruct virtio_transport_rx_pkt_ctx ctx;\n+\tstruct sockaddr_vm src, dst;\n+\tstruct sock *sk;\n+\tbool free_pkt;\n+\n+\t/* Only STREAM/RW packets can share a socket lock. */\n+\tif (le16_to_cpu(hdr-\u003etype) != VIRTIO_VSOCK_TYPE_STREAM ||\n+\t    le16_to_cpu(hdr-\u003eop) != VIRTIO_VSOCK_OP_RW) {\n+\t\tvirtio_transport_rx_batch_finish(batch);\n+\t\tvirtio_transport_recv_pkt(t, skb, net);\n+\t\treturn;\n+\t}\n+\n+\tvirtio_transport_recv_pkt_init_addrs(skb, \u0026src, \u0026dst);\n+\tvirtio_transport_trace_recv_pkt(skb, \u0026src, \u0026dst);\n+\n+\tif (batch-\u003esk) {\n+\t\tif (batch-\u003enet == net \u0026\u0026\n+\t\t    vsock_addr_equals_addr(\u0026batch-\u003esrc, \u0026src) \u0026\u0026\n+\t\t    vsock_addr_equals_addr(\u0026batch-\u003edst, \u0026dst) \u0026\u0026\n+\t\t    virtio_transport_recv_pkt_batchable(t, batch-\u003esk)) {\n+\t\t\tsk = batch-\u003esk;\n+\t\t\tdefer_data_ready = READ_ONCE(sk-\u003esk_data_ready) ==\n+\t\t\t\t\t   vsock_sk(sk)-\u003edefault_data_ready;\n+\t\t\tif (unlikely(batch-\u003edata_ready_pending \u0026\u0026 !defer_data_ready)) {\n+\t\t\t\t/*\n+\t\t\t\t * BPF map updates can replace callbacks while lock_sock()\n+\t\t\t\t * remains held. Finish the old notification first.\n+\t\t\t\t */\n+\t\t\t\tvirtio_transport_rx_batch_finish(batch);\n+\t\t\t\tgoto lookup;\n+\t\t\t}\n+\n+\t\t\tif (!skb_set_owner_sk_safe(skb, sk)) {\n+\t\t\t\tWARN_ONCE(1, \"receiving vsock socket has sk_refcnt == 0\\n\");\n+\t\t\t\tvirtio_transport_rx_batch_finish(batch);\n+\t\t\t\tkfree_skb(skb);\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\tctx = (struct virtio_transport_rx_pkt_ctx) {\n+\t\t\t\t.net = net,\n+\t\t\t\t.src = \u0026src,\n+\t\t\t\t.dst = \u0026dst,\n+\t\t\t\t.batchable = \u0026batchable,\n+\t\t\t\t.batch = batch,\n+\t\t\t\t.defer_data_ready = defer_data_ready,\n+\t\t\t};\n+\t\t\tfree_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, \u0026ctx);\n+\n+\t\t\tif (!batchable)\n+\t\t\t\tvirtio_transport_rx_batch_finish(batch);\n+\t\t\tif (free_pkt)\n+\t\t\t\tkfree_skb(skb);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tvirtio_transport_rx_batch_finish(batch);\n+\t}\n+\n+lookup:\n+\tsk = virtio_transport_recv_pkt_find_socket(skb, \u0026src, \u0026dst, net);\n+\tif (!sk) {\n+\t\tvirtio_transport_rx_batch_finish(batch);\n+\t\t(void)virtio_transport_reset_no_sock(t, skb, net);\n+\t\tkfree_skb(skb);\n+\t\treturn;\n+\t}\n+\n+\tif (!skb_set_owner_sk_safe(skb, sk)) {\n+\t\tWARN_ONCE(1, \"receiving vsock socket has sk_refcnt == 0\\n\");\n+\t\tkfree_skb(skb);\n+\t\treturn;\n+\t}\n+\n+\tlock_sock(sk);\n+\t/*\n+\t * Sockmap removal restores the native protocol under sk_callback_lock.\n+\t * Serialize this initial eligibility check with that update.\n+\t */\n+\tread_lock_bh(\u0026sk-\u003esk_callback_lock);\n+\tstart_batch = virtio_transport_recv_pkt_batchable(t, sk);\n+\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n+\n+\tif (start_batch)\n+\t\tbatch-\u003esk = sk;\n+\tdefer_data_ready = start_batch \u0026\u0026\n+\t\tREAD_ONCE(sk-\u003esk_data_ready) == vsock_sk(sk)-\u003edefault_data_ready;\n+\n+\tctx = (struct virtio_transport_rx_pkt_ctx) {\n+\t\t.net = net,\n+\t\t.src = \u0026src,\n+\t\t.dst = \u0026dst,\n+\t\t.batchable = start_batch ? \u0026batchable : NULL,\n+\t\t.batch = start_batch ? batch : NULL,\n+\t\t.defer_data_ready = defer_data_ready,\n+\t};\n+\tfree_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, \u0026ctx);\n+\tif (start_batch \u0026\u0026 batchable) {\n+\t\t/* Keep the lookup reference until the batch is released. */\n+\t\tbatch-\u003enet = net;\n+\t\tbatch-\u003esrc = src;\n+\t\tbatch-\u003edst = dst;\n+\t\treturn;\n+\t}\n+\n+\tif (start_batch) {\n+\t\tvirtio_transport_rx_batch_finish(batch);\n+\t} else {\n+\t\trelease_sock(sk);\n+\t\tsock_put(sk);\n+\t}\n+\n+\tif (free_pkt)\n+\t\tkfree_skb(skb);\n+}\n+EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt_batch);\n+\n /* Remove skbs found in a queue that have a vsk that matches.\n  *\n  * Each skb is freed.\ndiff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c\nindex 9049d26486460..7770a6c51dbe5 100644\n--- a/net/vmw_vsock/vsock_bpf.c\n+++ b/net/vmw_vsock/vsock_bpf.c\n@@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore\n \tif (restore) {\n \t\tsk-\u003esk_write_space = psock-\u003esaved_write_space;\n \t\tsock_replace_proto(sk, psock-\u003esk_proto);\n+\t\tvsock_rx_callback_kick(sk);\n \t\treturn 0;\n \t}\n \n@@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore\n \n \tvsock_bpf_check_needs_rebuild(psock-\u003esk_proto);\n \tsock_replace_proto(sk, \u0026vsock_bpf_prot);\n+\tvsock_rx_callback_kick(sk);\n \treturn 0;\n }\n \n"
}

2/1 2026/10/10 14:33 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VIRTIO_VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "BPF_SYSCALL",
    "VIRTIO"
  ],
  "FocusSymbols": [
    "virtio_transport_recv_pkt_batch",
    "virtio_transport_rx_batch_finish",
    "virtio_transport_recv_pkt",
    "vsock_rx_callback_handoff",
    "vsock_rx_cb_is_native",
    "vsock_bpf_update_proto"
  ],
  "Reasoning": "The patch introduces an RX packet batching mechanism for virtio vsock, allowing multiple packets to be processed under a single socket lock and deferring data_ready and write_space callbacks. It also adds callback handoff handling via delayed work when custom callbacks or BPF sockmap protocol replacements are active. These changes alter socket locking, state machine transitions, and callback synchronization across AF_VSOCK and virtio transports, which are fully reachable in standard virtualized environments (e.g., QEMU virtio-vsock and vsock loopback). Therefore, fuzzing is warranted to test for potential regressions, concurrency races, or state mismatches.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 976e9947786434b4e7066947337110fe5ba02405
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Oct 10 14:33:15 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057e..21a3460b940f2 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -282,6 +282,22 @@ void virtio_transport_destruct(struct vsock_sock *vsk);
 
 void virtio_transport_recv_pkt(struct virtio_transport *t,
 			       struct sk_buff *skb, struct net *net);
+
+struct virtio_transport_rx_batch {
+	struct sock *sk;
+	unsigned int pkts;
+	size_t bytes;
+	struct net *net;
+	struct sockaddr_vm src;
+	struct sockaddr_vm dst;
+	bool write_space_pending;
+	bool data_ready_pending;
+};
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+				     struct sk_buff *skb, struct net *net,
+				     struct virtio_transport_rx_batch *batch);
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch);
 void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);
 u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
 void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6b..86d6e6ba3d9df 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,6 +63,11 @@ struct vsock_sock {
 	u32 peer_shutdown;
 	bool sent_request;
 	bool ignore_connecting_rst;
+	/* Initial callbacks, used to identify replacements. */
+	void (*default_data_ready)(struct sock *sk);
+	void (*default_write_space)(struct sock *sk);
+	struct delayed_work rx_cb_work;
+	bool rx_cb_retried;
 
 	/* Protected by lock_sock(sk) */
 	u64 buffer_size;
@@ -78,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk);
 s64 vsock_stream_has_space(struct vsock_sock *vsk);
 struct sock *vsock_create_connected(struct sock *parent);
 void vsock_data_ready(struct sock *sk);
+bool vsock_rx_cb_is_native(struct sock *sk);
+void vsock_rx_callback_kick(struct sock *sk);
+void vsock_rx_callback_handoff(struct sock *sk);
 
 /**** TRANSPORT ****/
 
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44cee74519555..df6faee0bd37a 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 	return retval;
 }
 
+#define VSOCK_RX_CB_HANDOFF_MAX 64
+
+static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+
+	sock_hold(sk);
+	if (!schedule_delayed_work(&vsk->rx_cb_work, delay))
+		sock_put(sk);
+}
+
+void vsock_rx_callback_kick(struct sock *sk)
+{
+	vsock_rx_callback_queue(sk, 0);
+}
+
+/* Caller holds sk_callback_lock. */
+bool vsock_rx_cb_is_native(struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+
+	return READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+	       READ_ONCE(sk->sk_data_ready) == vsk->default_data_ready &&
+	       READ_ONCE(sk->sk_write_space) == vsk->default_write_space;
+}
+EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);
+
+/* Caller holds lock_sock(). */
+void vsock_rx_callback_handoff(struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*write_space)(struct sock *sk);
+	void (*data_ready)(struct sock *sk);
+	bool wrote = false;
+	s64 before, after;
+	int i;
+
+	if (sock_flag(sk, SOCK_DEAD) || !vsk->transport)
+		return;
+
+	read_lock_bh(&sk->sk_callback_lock);
+	data_ready = READ_ONCE(sk->sk_data_ready);
+	if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator &&
+	    data_ready == vsk->default_data_ready) {
+		read_unlock_bh(&sk->sk_callback_lock);
+		/*
+		 * start_verdict publishes sk_data_ready after the proto swap.
+		 * Retry once before treating this as a stable non-RX setup.
+		 */
+		if (!vsk->rx_cb_retried) {
+			vsk->rx_cb_retried = true;
+			vsock_rx_callback_queue(sk, 1);
+			return;
+		}
+		vsk->rx_cb_retried = false;
+		data_ready(sk);
+		return;
+	}
+	read_unlock_bh(&sk->sk_callback_lock);
+	vsk->rx_cb_retried = false;
+
+	for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) {
+		read_lock_bh(&sk->sk_callback_lock);
+		data_ready = READ_ONCE(sk->sk_data_ready);
+		write_space = READ_ONCE(sk->sk_write_space);
+		read_unlock_bh(&sk->sk_callback_lock);
+
+		if (!wrote && write_space != vsk->default_write_space) {
+			write_space(sk);
+			wrote = true;
+		}
+		if (data_ready == vsk->default_data_ready) {
+			data_ready(sk);
+			break;
+		}
+		before = vsock_stream_has_data(vsk);
+		if (before <= 0)
+			break;
+		data_ready(sk);
+		after = vsock_stream_has_data(vsk);
+		if (after >= before)
+			break;
+		if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0)
+			vsock_rx_callback_queue(sk, 0);
+	}
+}
+EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff);
+
+static void vsock_rx_callback_work(struct work_struct *work)
+{
+	struct vsock_sock *vsk = container_of(work, struct vsock_sock,
+					      rx_cb_work.work);
+	struct sock *sk = sk_vsock(vsk);
+
+	lock_sock(sk);
+	if (!sock_flag(sk, SOCK_DEAD))
+		vsock_rx_callback_handoff(sk);
+	release_sock(sk);
+	sock_put(sk);
+}
+
 static void vsock_connect_timeout(struct work_struct *work);
 
 static struct sock *__vsock_create(struct net *net,
@@ -958,6 +1059,8 @@ static struct sock *__vsock_create(struct net *net,
 		sk->sk_type = type;
 
 	vsk = vsock_sk(sk);
+	vsk->default_data_ready = sk->sk_data_ready;
+	vsk->default_write_space = sk->sk_write_space;
 	vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
 	vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
 
@@ -975,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net,
 	WRITE_ONCE(vsk->peer_shutdown, 0);
 	INIT_DELAYED_WORK(&vsk->connect_work, vsock_connect_timeout);
 	INIT_DELAYED_WORK(&vsk->pending_work, vsock_pending_work);
+	INIT_DELAYED_WORK(&vsk->rx_cb_work, vsock_rx_callback_work);
 
 	psk = parent ? vsock_sk(parent) : NULL;
 	if (parent) {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa5..ef076732158d4 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
 	return seqpacket_allow;
 }
 
+/*
+ * Keep a bounded run of packets for one socket under a single socket lock.
+ * Limit packet count and payload size to bound the work done while locked.
+ */
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS	64
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES	(64 * 1024)
+
 static void virtio_transport_rx_work(struct work_struct *work)
 {
+	struct virtio_transport_rx_batch batch = {};
 	struct virtio_vsock *vsock =
 		container_of(work, struct virtio_vsock, rx_work);
 	struct virtqueue *vq;
@@ -666,6 +674,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			/* Drop short/long packets */
 			if (unlikely(len < sizeof(*hdr) ||
 				     len > virtio_vsock_skb_len(skb))) {
+				virtio_transport_rx_batch_finish(&batch);
 				kfree_skb(skb);
 				continue;
 			}
@@ -673,6 +682,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			hdr = virtio_vsock_hdr(skb);
 			payload_len = le32_to_cpu(hdr->len);
 			if (unlikely(payload_len > len - sizeof(*hdr))) {
+				virtio_transport_rx_batch_finish(&batch);
 				kfree_skb(skb);
 				continue;
 			}
@@ -680,16 +690,33 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			if (payload_len)
 				virtio_vsock_skb_put(skb, payload_len);
 
+			if (batch.sk &&
+			    payload_len > VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES -
+					  batch.bytes) {
+				virtio_transport_rx_batch_finish(&batch);
+			}
+
 			virtio_transport_deliver_tap_pkt(skb);
 
 			/* Force virtio-transport into global mode since it
 			 * does not yet support local-mode namespacing.
 			 */
-			virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+			virtio_transport_recv_pkt_batch(&virtio_transport, skb, NULL,
+							&batch);
+
+			if (batch.sk) {
+				batch.pkts++;
+				batch.bytes += payload_len;
+				if (batch.pkts >= VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS ||
+				    batch.bytes >= VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES) {
+					virtio_transport_rx_batch_finish(&batch);
+				}
+			}
 		}
 	} while (!virtqueue_enable_cb(vq));
 
 out:
+	virtio_transport_rx_batch_finish(&batch);
 	if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
 		virtio_vsock_rx_fill(vsock);
 out_nofill:
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f225f53ed4bab..6b96d8ac0d598 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
 	skb_queue_splice(&new_queue, &vvs->rx_queue);
 }
 
+static bool
+virtio_transport_rx_skb_has_headroom(struct virtio_vsock_sock *vvs)
+{
+	return (u64)(skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0) <=
+		vvs->buf_alloc;
+}
+
 static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
 					u32 len)
 {
-	u64 skb_overhead = (skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0);
 
 	/* Allow at most buf_alloc * 2 total budget (payload + overhead),
 	 * similar to how SO_RCVBUF is doubled to reserve space for sk_buff
@@ -588,7 +594,7 @@ static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
 	 * queue growth.
 	 */
 	if ((u64)vvs->buf_used + len > vvs->buf_alloc ||
-	    skb_overhead > vvs->buf_alloc)
+	    !virtio_transport_rx_skb_has_headroom(vvs))
 		return false;
 
 	vvs->rx_bytes += len;
@@ -1583,10 +1589,12 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,
 
 static int
 virtio_transport_recv_connected(struct sock *sk,
-				struct sk_buff *skb)
+				struct sk_buff *skb,
+				bool *data_ready_pending)
 {
 	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
 	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*data_ready)(struct sock *sk);
 	int err = 0;
 
 	switch (le16_to_cpu(hdr->op)) {
@@ -1602,7 +1610,23 @@ virtio_transport_recv_connected(struct sock *sk,
 			vsock_remove_sock(vsk);
 			break;
 		}
-		vsock_data_ready(sk);
+		if (!data_ready_pending) {
+			vsock_data_ready(sk);
+		} else {
+			data_ready = READ_ONCE(sk->sk_data_ready);
+			if (data_ready == vsk->default_data_ready) {
+				if (!*data_ready_pending &&
+				    (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				     sock_flag(sk, SOCK_DONE)))
+					*data_ready_pending = true;
+			} else {
+				/* Use the callback seen for this packet. */
+				*data_ready_pending = false;
+				if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				    sock_flag(sk, SOCK_DONE))
+					data_ready(sk);
+			}
+		}
 		return err;
 	case VIRTIO_VSOCK_OP_CREDIT_REQUEST:
 		virtio_transport_send_credit_update(vsk);
@@ -1774,84 +1798,133 @@ static bool virtio_transport_valid_type(u16 type)
 	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);
 }
 
-/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
- * lock.
- */
-void virtio_transport_recv_pkt(struct virtio_transport *t,
-			       struct sk_buff *skb, struct net *net)
+static void
+virtio_transport_recv_pkt_init_addrs(struct sk_buff *skb,
+				     struct sockaddr_vm *src,
+				     struct sockaddr_vm *dst)
 {
 	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
-	struct sockaddr_vm src, dst;
-	struct vsock_sock *vsk;
-	struct sock *sk;
-	bool space_available;
 
-	vsock_addr_init(&src, le64_to_cpu(hdr->src_cid),
+	vsock_addr_init(src, le64_to_cpu(hdr->src_cid),
 			le32_to_cpu(hdr->src_port));
-	vsock_addr_init(&dst, le64_to_cpu(hdr->dst_cid),
+	vsock_addr_init(dst, le64_to_cpu(hdr->dst_cid),
 			le32_to_cpu(hdr->dst_port));
+}
 
-	trace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,
-					dst.svm_cid, dst.svm_port,
+static void
+virtio_transport_trace_recv_pkt(struct sk_buff *skb,
+				const struct sockaddr_vm *src,
+				const struct sockaddr_vm *dst)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+
+	trace_virtio_transport_recv_pkt(src->svm_cid, src->svm_port,
+					dst->svm_cid, dst->svm_port,
 					le32_to_cpu(hdr->len),
 					le16_to_cpu(hdr->type),
 					le16_to_cpu(hdr->op),
 					le32_to_cpu(hdr->flags),
 					le32_to_cpu(hdr->buf_alloc),
 					le32_to_cpu(hdr->fwd_cnt));
+}
 
-	if (!virtio_transport_valid_type(le16_to_cpu(hdr->type))) {
-		(void)virtio_transport_reset_no_sock(t, skb, net);
-		goto free_pkt;
-	}
+static struct sock *
+virtio_transport_recv_pkt_find_socket(struct sk_buff *skb,
+				      struct sockaddr_vm *src,
+				      struct sockaddr_vm *dst,
+				      struct net *net)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+	struct sock *sk;
 
-	/* The socket must be in connected or bound table
-	 * otherwise send reset back
-	 */
-	sk = vsock_find_connected_socket_net(&src, &dst, net);
-	if (!sk) {
-		sk = vsock_find_bound_socket_net(&dst, net);
-		if (!sk) {
-			(void)virtio_transport_reset_no_sock(t, skb, net);
-			goto free_pkt;
-		}
-	}
+	if (!virtio_transport_valid_type(le16_to_cpu(hdr->type)))
+		return NULL;
+
+	sk = vsock_find_connected_socket_net(src, dst, net);
+	if (!sk)
+		sk = vsock_find_bound_socket_net(dst, net);
+	if (!sk)
+		return NULL;
 
 	if (virtio_transport_get_type(sk) != le16_to_cpu(hdr->type)) {
-		(void)virtio_transport_reset_no_sock(t, skb, net);
 		sock_put(sk);
-		goto free_pkt;
+		return NULL;
 	}
 
-	if (!skb_set_owner_sk_safe(skb, sk)) {
-		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
-		goto free_pkt;
-	}
+	return sk;
+}
 
-	vsk = vsock_sk(sk);
+struct virtio_transport_rx_pkt_ctx {
+	struct net *net;
+	const struct sockaddr_vm *src;
+	const struct sockaddr_vm *dst;
+	bool *batchable;
+	struct virtio_transport_rx_batch *batch;
+	bool defer_data_ready;
+};
 
-	lock_sock(sk);
+static bool
+virtio_transport_recv_pkt_batchable(struct virtio_transport *t,
+				    struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+	struct virtio_vsock_sock *vvs = vsk->trans;
+
+	return sk->sk_state == TCP_ESTABLISHED &&
+	       sk->sk_type == SOCK_STREAM &&
+	       READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+	       !sock_flag(sk, SOCK_DONE) &&
+	       vsk->transport == &t->transport &&
+	       vvs && virtio_transport_rx_skb_has_headroom(vvs);
+}
+
+/*
+ * The caller holds sk's socket lock.  Set @batchable if the socket can remain
+ * locked for another ordinary STREAM/RW packet.  Return true if the caller
+ * must free @skb.
+ */
+static bool
+virtio_transport_recv_pkt_locked(struct virtio_transport *t,
+				 struct sk_buff *skb, struct sock *sk,
+				 const struct virtio_transport_rx_pkt_ctx *ctx)
+{
+	const struct sockaddr_vm *src = ctx->src;
+	const struct sockaddr_vm *dst = ctx->dst;
+	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*write_space)(struct sock *sk);
+	struct net *net = ctx->net;
+	bool space_available;
 
-	/* Check if sk has been closed or assigned to another transport before
-	 * lock_sock (note: listener sockets are not assigned to any transport)
+	if (ctx->batchable)
+		*ctx->batchable = false;
+
+	/* Check after acquiring the socket lock. Listener sockets accept packets
+	 * from any source and are not assigned to a transport.
 	 */
 	if (sock_flag(sk, SOCK_DONE) ||
 	    (sk->sk_state != TCP_LISTEN &&
-	     !vsock_check_source(vsk, &t->transport, &src))) {
+	     !vsock_check_source(vsk, &t->transport, src))) {
 		(void)virtio_transport_reset_no_sock(t, skb, net);
-		release_sock(sk);
-		sock_put(sk);
-		goto free_pkt;
+		return true;
 	}
 
 	space_available = virtio_transport_space_update(sk, skb);
 
 	/* Update CID in case it has changed after a transport reset event */
 	if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
-		vsk->local_addr.svm_cid = dst.svm_cid;
-
-	if (space_available)
-		sk->sk_write_space(sk);
+		vsk->local_addr.svm_cid = dst->svm_cid;
+
+	if (space_available) {
+		write_space = READ_ONCE(sk->sk_write_space);
+		if (ctx->batch &&
+		    write_space == vsk->default_write_space &&
+		    virtio_transport_recv_pkt_batchable(t, sk)) {
+			ctx->batch->write_space_pending = true;
+		} else {
+			/* Use the callback seen for this packet. */
+			write_space(sk);
+		}
+	}
 
 	switch (sk->sk_state) {
 	case TCP_LISTEN:
@@ -1863,7 +1936,9 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		kfree_skb(skb);
 		break;
 	case TCP_ESTABLISHED:
-		virtio_transport_recv_connected(sk, skb);
+		virtio_transport_recv_connected(sk, skb,
+						ctx->defer_data_ready && ctx->batch ?
+						&ctx->batch->data_ready_pending : NULL);
 		break;
 	case TCP_CLOSING:
 		virtio_transport_recv_disconnecting(sk, skb);
@@ -1875,12 +1950,52 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		break;
 	}
 
+	if (ctx->batchable)
+		*ctx->batchable = virtio_transport_recv_pkt_batchable(t, sk);
+
+	return false;
+}
+
+/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
+ * lock.
+ */
+void virtio_transport_recv_pkt(struct virtio_transport *t,
+			       struct sk_buff *skb, struct net *net)
+{
+	struct virtio_transport_rx_pkt_ctx ctx;
+	struct sockaddr_vm src, dst;
+	struct sock *sk;
+	bool free_pkt;
+
+	virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+	virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+	sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+	if (!sk) {
+		(void)virtio_transport_reset_no_sock(t, skb, net);
+		goto free_pkt;
+	}
+
+	if (!skb_set_owner_sk_safe(skb, sk)) {
+		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+		goto free_pkt;
+	}
+
+	lock_sock(sk);
+	ctx = (struct virtio_transport_rx_pkt_ctx) {
+		.net = net,
+		.src = &src,
+		.dst = &dst,
+	};
+	free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
 	release_sock(sk);
 
 	/* Release refcnt obtained when we fetched this socket out of the
 	 * bound or connected list.
 	 */
 	sock_put(sk);
+	if (free_pkt)
+		kfree_skb(skb);
 	return;
 
 free_pkt:
@@ -1888,6 +2003,178 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 }
 EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
 
+/*
+ * Finish the RX batch.
+ * For a non-empty batch, the caller must hold the socket lock acquired with
+ * lock_sock(). This function releases the lock and the batch's lookup
+ * reference. An empty batch is a no-op.
+ */
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
+{
+	bool write_space_pending = batch->write_space_pending;
+	bool data_ready_pending = batch->data_ready_pending;
+	struct sock *sk = batch->sk;
+	struct vsock_sock *vsk;
+	bool native;
+
+	batch->sk = NULL;
+	batch->pkts = 0;
+	batch->bytes = 0;
+	batch->net = NULL;
+	batch->write_space_pending = false;
+	batch->data_ready_pending = false;
+
+	if (!sk)
+		return;
+
+	vsk = vsock_sk(sk);
+	if (write_space_pending)
+		vsk->default_write_space(sk);
+
+	read_lock_bh(&sk->sk_callback_lock);
+	native = vsock_rx_cb_is_native(sk);
+	read_unlock_bh(&sk->sk_callback_lock);
+
+	if (native) {
+		release_sock(sk);
+		if (data_ready_pending)
+			vsk->default_data_ready(sk);
+		sock_put(sk);
+		return;
+	}
+
+	if (write_space_pending || data_ready_pending)
+		vsock_rx_callback_handoff(sk);
+
+	release_sock(sk);
+	sock_put(sk);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_rx_batch_finish);
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+				     struct sk_buff *skb, struct net *net,
+				     struct virtio_transport_rx_batch *batch)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+	bool batchable, defer_data_ready, start_batch;
+	struct virtio_transport_rx_pkt_ctx ctx;
+	struct sockaddr_vm src, dst;
+	struct sock *sk;
+	bool free_pkt;
+
+	/* Only STREAM/RW packets can share a socket lock. */
+	if (le16_to_cpu(hdr->type) != VIRTIO_VSOCK_TYPE_STREAM ||
+	    le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW) {
+		virtio_transport_rx_batch_finish(batch);
+		virtio_transport_recv_pkt(t, skb, net);
+		return;
+	}
+
+	virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+	virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+	if (batch->sk) {
+		if (batch->net == net &&
+		    vsock_addr_equals_addr(&batch->src, &src) &&
+		    vsock_addr_equals_addr(&batch->dst, &dst) &&
+		    virtio_transport_recv_pkt_batchable(t, batch->sk)) {
+			sk = batch->sk;
+			defer_data_ready = READ_ONCE(sk->sk_data_ready) ==
+					   vsock_sk(sk)->default_data_ready;
+			if (unlikely(batch->data_ready_pending && !defer_data_ready)) {
+				/*
+				 * BPF map updates can replace callbacks while lock_sock()
+				 * remains held. Finish the old notification first.
+				 */
+				virtio_transport_rx_batch_finish(batch);
+				goto lookup;
+			}
+
+			if (!skb_set_owner_sk_safe(skb, sk)) {
+				WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+				virtio_transport_rx_batch_finish(batch);
+				kfree_skb(skb);
+				return;
+			}
+
+			ctx = (struct virtio_transport_rx_pkt_ctx) {
+				.net = net,
+				.src = &src,
+				.dst = &dst,
+				.batchable = &batchable,
+				.batch = batch,
+				.defer_data_ready = defer_data_ready,
+			};
+			free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+
+			if (!batchable)
+				virtio_transport_rx_batch_finish(batch);
+			if (free_pkt)
+				kfree_skb(skb);
+			return;
+		}
+
+		virtio_transport_rx_batch_finish(batch);
+	}
+
+lookup:
+	sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+	if (!sk) {
+		virtio_transport_rx_batch_finish(batch);
+		(void)virtio_transport_reset_no_sock(t, skb, net);
+		kfree_skb(skb);
+		return;
+	}
+
+	if (!skb_set_owner_sk_safe(skb, sk)) {
+		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+		kfree_skb(skb);
+		return;
+	}
+
+	lock_sock(sk);
+	/*
+	 * Sockmap removal restores the native protocol under sk_callback_lock.
+	 * Serialize this initial eligibility check with that update.
+	 */
+	read_lock_bh(&sk->sk_callback_lock);
+	start_batch = virtio_transport_recv_pkt_batchable(t, sk);
+	read_unlock_bh(&sk->sk_callback_lock);
+
+	if (start_batch)
+		batch->sk = sk;
+	defer_data_ready = start_batch &&
+		READ_ONCE(sk->sk_data_ready) == vsock_sk(sk)->default_data_ready;
+
+	ctx = (struct virtio_transport_rx_pkt_ctx) {
+		.net = net,
+		.src = &src,
+		.dst = &dst,
+		.batchable = start_batch ? &batchable : NULL,
+		.batch = start_batch ? batch : NULL,
+		.defer_data_ready = defer_data_ready,
+	};
+	free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+	if (start_batch && batchable) {
+		/* Keep the lookup reference until the batch is released. */
+		batch->net = net;
+		batch->src = src;
+		batch->dst = dst;
+		return;
+	}
+
+	if (start_batch) {
+		virtio_transport_rx_batch_finish(batch);
+	} else {
+		release_sock(sk);
+		sock_put(sk);
+	}
+
+	if (free_pkt)
+		kfree_skb(skb);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt_batch);
+
 /* Remove skbs found in a queue that have a vsk that matches.
  *
  * Each skb is freed.
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d26486460..7770a6c51dbe5 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 	if (restore) {
 		sk->sk_write_space = psock->saved_write_space;
 		sock_replace_proto(sk, psock->sk_proto);
+		vsock_rx_callback_kick(sk);
 		return 0;
 	}
 
@@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 
 	vsock_bpf_check_needs_rebuild(psock->sk_proto);
 	sock_replace_proto(sk, &vsock_bpf_prot);
+	vsock_rx_callback_kick(sk);
 	return 0;
 }
 

3/2 2026/10/10 14:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 12582
							output: 56
							thoughts: 317
						

Thoughts:
**Evaluating the Virtio-VSOCK Batching Patch**

Okay, so I need to dig into this patch series applied at HEAD in the kernel repo. First things first, I need to get a clear picture of what this patch is actually *doing*. Let's see... it looks like the files that have been modified are: `include/linux/virtio_vsock.h`, `include/net/af_vsock.h`, `net/vmw_vsock/af_vsock.c`, `net/vmw_vsock/virtio_transport.c`, `net/vmw_vsock/virtio_transport_common.c`, and `net/vmw_vsock/vsock_bpf.c`. So, a mix of headers and core implementation files.

Hmm, the description says this patch is adding batching to virtio vsock's receive processing.  I see functions like `virtio_transport_recv_pkt_batch`, `virtio_transport_rx_batch_finish`, and `vsock_rx_callback_handoff`.  There's also some work related to `rx_cb_work` and how this interacts with sockmap/BPF through `vsock_bpf_update_proto` to kick RX callbacks. That's a reasonable set of changes to implement batching.

Now, a key question here is, how readily accessible is this code for testing? We need to evaluate its exposure. Can this be easily reached in standard environments like QEMU or fuzzing setups like syzkaller?  Good news, it looks like virtio-vsock is a standard virtual device in QEMU, so that's promising. And yes, vsock sockets are part of AF_VSOCK, which is well-defined.

But I need to confirm: does syzkaller actually *use* vsock loopback or virtio-vsock or vhost-vsock?  Let me quickly check the Kconfig files associated with those modules. I need to understand what configurations and drivers enable them to understand their coverage.



4/2 2026/10/10 14:33 tool grepper
0m Args:
{
  "Expression": "config VHOST_VSOCK|config VIRTIO_VSOCKETS",
  "PathPrefixes": [
    "net/vmw_vsock/Kconfig",
    "drivers/vhost/Kconfig"
  ]
}

Results:
{
  "Output": "drivers/vhost/Kconfig=46=config VHOST_SCSI\n--\ndrivers/vhost/Kconfig-55-\ndrivers/vhost/Kconfig:56:config VHOST_VSOCK\ndrivers/vhost/Kconfig-57-\ttristate \"vhost virtio-vsock driver\"\n--\nnet/vmw_vsock/Kconfig=41=config VMWARE_VMCI_VSOCKETS\n--\nnet/vmw_vsock/Kconfig-52-\nnet/vmw_vsock/Kconfig:53:config VIRTIO_VSOCKETS\nnet/vmw_vsock/Kconfig-54-\ttristate \"virtio transport for Virtual Sockets\"\n--\nnet/vmw_vsock/Kconfig-65-\nnet/vmw_vsock/Kconfig:66:config VIRTIO_VSOCKETS_COMMON\nnet/vmw_vsock/Kconfig-67-\ttristate\n"
}

5/2 2026/10/10 14:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 13646
							output: 36
							thoughts: 28
						

Thoughts:
Here's a summary of my thought process:

**Diving into the vmw_vsock Implementation**

Okay, I need to get a handle on this `vmw_vsock` situation. My immediate instinct is to look at the foundational files. Specifically, I need to understand how this module is configured and built. So, I'm heading straight for the `Kconfig` and `Makefile` within the `net/vmw_vsock` directory. The `Kconfig` file will show me the configuration options, the dependencies, and how this feature is enabled or disabled at build time. The `Makefile`, on the other hand, contains the build rules, source file list, and how the module is compiled and linked. Grasping these two pieces first is the essential foundational step before I can dig any deeper and start to understand the internal structure of the `vmw_vsock` implementation. These are the necessary first questions that I need answered.



6/2 2026/10/10 14:33 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/Kconfig",
  "FirstLine": 1,
  "LineCount": 80
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# Vsock protocol\n   4:\t#\n   5:\t\n   6:\tconfig VSOCKETS\n   7:\t\ttristate \"Virtual Socket protocol\"\n   8:\t\thelp\n   9:\t\t  Virtual Socket Protocol is a socket protocol similar to TCP/IP\n  10:\t\t  allowing communication between Virtual Machines and hypervisor\n  11:\t\t  or host.\n  12:\t\n  13:\t\t  You should also select one or more hypervisor-specific transports\n  14:\t\t  below.\n  15:\t\n  16:\t\t  To compile this driver as a module, choose M here: the module\n  17:\t\t  will be called vsock. If unsure, say N.\n  18:\t\n  19:\tconfig VSOCKETS_DIAG\n  20:\t\ttristate \"Virtual Sockets monitoring interface\"\n  21:\t\tdepends on VSOCKETS\n  22:\t\tdefault y\n  23:\t\thelp\n  24:\t\t  Support for PF_VSOCK sockets monitoring interface used by the ss tool.\n  25:\t\t  If unsure, say Y.\n  26:\t\n  27:\t\t  Enable this module so userspace applications can query open sockets.\n  28:\t\n  29:\tconfig VSOCKETS_LOOPBACK\n  30:\t\ttristate \"Virtual Sockets loopback transport\"\n  31:\t\tdepends on VSOCKETS\n  32:\t\tdefault y\n  33:\t\tselect VIRTIO_VSOCKETS_COMMON\n  34:\t\thelp\n  35:\t\t  This module implements a loopback transport for Virtual Sockets,\n  36:\t\t  using vmw_vsock_virtio_transport_common.\n  37:\t\n  38:\t\t  To compile this driver as a module, choose M here: the module\n  39:\t\t  will be called vsock_loopback. If unsure, say N.\n  40:\t\n  41:\tconfig VMWARE_VMCI_VSOCKETS\n  42:\t\ttristate \"VMware VMCI transport for Virtual Sockets\"\n  43:\t\tdepends on VSOCKETS \u0026\u0026 VMWARE_VMCI\n  44:\t\thelp\n  45:\t\t  This module implements a VMCI transport for Virtual Sockets.\n  46:\t\n  47:\t\t  Enable this transport if your Virtual Machine runs on a VMware\n  48:\t\t  hypervisor.\n  49:\t\n  50:\t\t  To compile this driver as a module, choose M here: the module\n  51:\t\t  will be called vmw_vsock_vmci_transport. If unsure, say N.\n  52:\t\n  53:\tconfig VIRTIO_VSOCKETS\n  54:\t\ttristate \"virtio transport for Virtual Sockets\"\n  55:\t\tdepends on VSOCKETS \u0026\u0026 VIRTIO\n  56:\t\tselect VIRTIO_VSOCKETS_COMMON\n  57:\t\thelp\n  58:\t\t  This module implements a virtio transport for Virtual Sockets.\n  59:\t\n  60:\t\t  Enable this transport if your Virtual Machine host supports Virtual\n  61:\t\t  Sockets over virtio.\n  62:\t\n  63:\t\t  To compile this driver as a module, choose M here: the module will be\n  64:\t\t  called vmw_vsock_virtio_transport. If unsure, say N.\n  65:\t\n  66:\tconfig VIRTIO_VSOCKETS_COMMON\n  67:\t\ttristate\n  68:\t\thelp\n  69:\t\t  This option is selected by any driver which needs to access\n  70:\t\t  the virtio_vsock.  The module will be called\n  71:\t\t  vmw_vsock_virtio_transport_common.\n  72:\t\n  73:\tconfig HYPERV_VSOCKETS\n  74:\t\ttristate \"Hyper-V transport for Virtual Sockets\"\n  75:\t\tdepends on VSOCKETS \u0026\u0026 HYPERV_VMBUS\n  76:\t\thelp\n  77:\t\t  This module implements a Hyper-V transport for Virtual Sockets.\n  78:\t\n  79:\t\t  Enable this transport if your Virtual Machine host supports Virtual\n  80:\t\t  Sockets over Hyper-V VMBus.\n"
}

7/2 2026/10/10 14:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14985
							output: 35
							thoughts: 15
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into the vmw_vsock Makefile**

Alright, let's get down to brass tacks. My initial thought is to navigate straight to the `net/vmw_vsock/Makefile`. Considering the context, it's pretty clear that this is where the core build configurations and dependencies for the VMware vsock driver reside. I'm going to be looking for clues regarding kernel module compilation, potentially cross-compilation directives if this is for a hypervisor environment, and any architecture-specific flags. I'll be particularly interested in:

*   **Dependencies:** Are there any external libraries or other kernel modules it relies upon? I want to understand the module's footprint.
*   **Compilation Flags:** What compiler options are being used? Are there optimization flags, debugging symbols, or specific warnings being enabled or suppressed? This gives an indication of the build's characteristics.
*   **Source Files:** A quick overview of the source files involved is a given; I'll need to confirm what's in this directory.
*   **Conditional Compilation:** I'm expecting to see preprocessor directives based on kernel versions, target architectures, or VMware hypervisor versions. I'll be checking this to understand the driver's flexibility.
*   **Installation Instructions:** I would like to see how the module is designed to be installed.
*   **Error Handling:** It would be good to understand any design considerations.

This Makefile is the key to understanding how this driver gets built and integrated into the kernel. It’s the foundation for any deeper investigation into the code itself.



8/2 2026/10/10 14:33 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/Makefile",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\tobj-$(CONFIG_VSOCKETS) += vsock.o\n   3:\tobj-$(CONFIG_VSOCKETS_DIAG) += vsock_diag.o\n   4:\tobj-$(CONFIG_VMWARE_VMCI_VSOCKETS) += vmw_vsock_vmci_transport.o\n   5:\tobj-$(CONFIG_VIRTIO_VSOCKETS) += vmw_vsock_virtio_transport.o\n   6:\tobj-$(CONFIG_VIRTIO_VSOCKETS_COMMON) += vmw_vsock_virtio_transport_common.o\n   7:\tobj-$(CONFIG_HYPERV_VSOCKETS) += hv_sock.o\n   8:\tobj-$(CONFIG_VSOCKETS_LOOPBACK) += vsock_loopback.o\n   9:\t\n  10:\tvsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o\n  11:\tvsock-$(CONFIG_BPF_SYSCALL) += vsock_bpf.o\n  12:\t\n  13:\tvsock_diag-y += diag.o\n  14:\t\n  15:\tvmw_vsock_vmci_transport-y += vmci_transport.o vmci_transport_notify.o \\\n  16:\t\tvmci_transport_notify_qstate.o\n  17:\t\n  18:\tvmw_vsock_virtio_transport-y += virtio_transport.o\n  19:\t\n  20:\tvmw_vsock_virtio_transport_common-y += virtio_transport_common.o\n  21:\t\n  22:\thv_sock-y += hyperv_transport.o\n"
}

9/2 2026/10/10 14:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15840
							output: 41
							thoughts: 229
						

Thoughts:
**Examining the vsock Module Dependencies and Data Flow**

Okay, let's break this down. First, I see the module dependencies: `obj-$(CONFIG_VSOCKETS) += vsock.o`, `obj-$(CONFIG_VIRTIO_VSOCKETS) += vmw_vsock_virtio_transport.o`, and the related files. Nothing unexpected there, good, good. I'm taking note of the `CONFIG_` preprocessor directives; they’ll inform what gets built where. The `vsock-$(CONFIG_BPF_SYSCALL) += vsock_bpf.o` line is interesting – BPF integration for vsock, that's something to keep an eye on, potentially for performance analysis or filtering.

Now, I'm diving deeper into the `virtio_transport.c` file within the `net/vmw_vsock` directory. Specifically, I'm focused on the receive path, looking at `virtio_transport_rx_work`. This function calls `virtio_transport_recv_pkt_batch` followed by `virtio_transport_rx_batch_finish`. This suggests a batching approach for receive operations, which is sensible and can improve efficiency. Okay, makes sense.

Wait a second...I'm getting a nagging feeling. *Am I correct in my assumption that* `vsock_loopback.c` *is using the* `virtio_transport_recv_pkt` *function, too?* I need to verify that; it's possible the loopback path is bypassing the same batching optimization and could become a bottleneck. Time to pull up `vsock_loopback.c` and confirm. Let's see...



10/2 2026/10/10 14:33 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_loopback.c",
  "FirstLine": 1,
  "LineCount": 80
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0-only\n   2:\t/* loopback transport for vsock using virtio_transport_common APIs\n   3:\t *\n   4:\t * Copyright (C) 2013-2019 Red Hat, Inc.\n   5:\t * Authors: Asias He \u003casias@redhat.com\u003e\n   6:\t *          Stefan Hajnoczi \u003cstefanha@redhat.com\u003e\n   7:\t *          Stefano Garzarella \u003csgarzare@redhat.com\u003e\n   8:\t *\n   9:\t */\n  10:\t#include \u003clinux/spinlock.h\u003e\n  11:\t#include \u003clinux/module.h\u003e\n  12:\t#include \u003clinux/list.h\u003e\n  13:\t#include \u003clinux/virtio_vsock.h\u003e\n  14:\t\n  15:\tstruct vsock_loopback {\n  16:\t\tstruct workqueue_struct *workqueue;\n  17:\t\n  18:\t\tstruct sk_buff_head pkt_queue;\n  19:\t\tstruct work_struct pkt_work;\n  20:\t};\n  21:\t\n  22:\tstatic struct vsock_loopback the_vsock_loopback;\n  23:\t\n  24:\tstatic u32 vsock_loopback_get_local_cid(void)\n  25:\t{\n  26:\t\treturn VMADDR_CID_LOCAL;\n  27:\t}\n  28:\t\n  29:\tstatic int vsock_loopback_send_pkt(struct sk_buff *skb, struct net *net)\n  30:\t{\n  31:\t\tstruct vsock_loopback *vsock = \u0026the_vsock_loopback;\n  32:\t\tint len = skb-\u003elen;\n  33:\t\n  34:\t\tvirtio_vsock_skb_queue_tail(\u0026vsock-\u003epkt_queue, skb);\n  35:\t\tqueue_work(vsock-\u003eworkqueue, \u0026vsock-\u003epkt_work);\n  36:\t\n  37:\t\treturn len;\n  38:\t}\n  39:\t\n  40:\tstatic int vsock_loopback_cancel_pkt(struct vsock_sock *vsk)\n  41:\t{\n  42:\t\tstruct vsock_loopback *vsock = \u0026the_vsock_loopback;\n  43:\t\n  44:\t\tvirtio_transport_purge_skbs(vsk, \u0026vsock-\u003epkt_queue);\n  45:\t\n  46:\t\treturn 0;\n  47:\t}\n  48:\t\n  49:\tstatic bool vsock_loopback_seqpacket_allow(struct vsock_sock *vsk,\n  50:\t\t\t\t\t\t   u32 remote_cid);\n  51:\t\n  52:\tstatic bool vsock_loopback_stream_allow(struct vsock_sock *vsk, u32 cid,\n  53:\t\t\t\t\t\tu32 port)\n  54:\t{\n  55:\t\treturn true;\n  56:\t}\n  57:\t\n  58:\tstatic bool vsock_loopback_msgzerocopy_allow(void)\n  59:\t{\n  60:\t\treturn true;\n  61:\t}\n  62:\t\n  63:\tstatic struct virtio_transport loopback_transport = {\n  64:\t\t.transport = {\n  65:\t\t\t.module                   = THIS_MODULE,\n  66:\t\n  67:\t\t\t.get_local_cid            = vsock_loopback_get_local_cid,\n  68:\t\n  69:\t\t\t.init                     = virtio_transport_do_socket_init,\n  70:\t\t\t.destruct                 = virtio_transport_destruct,\n  71:\t\t\t.release                  = virtio_transport_release,\n  72:\t\t\t.connect                  = virtio_transport_connect,\n  73:\t\t\t.shutdown                 = virtio_transport_shutdown,\n  74:\t\t\t.cancel_pkt               = vsock_loopback_cancel_pkt,\n  75:\t\n  76:\t\t\t.dgram_bind               = virtio_transport_dgram_bind,\n  77:\t\t\t.dgram_dequeue            = virtio_transport_dgram_dequeue,\n  78:\t\t\t.dgram_enqueue            = virtio_transport_dgram_enqueue,\n  79:\t\t\t.dgram_allow              = virtio_transport_dgram_allow,\n  80:\t\n"
}

11/2 2026/10/10 14:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17625
							output: 42
							thoughts: 21
						

Thoughts:
Okay, let's dive into this. Here's my thought process on dissecting `vsock_loopback.c` and its `pkt_work` function:

**Dissecting `vsock_loopback.c` & `pkt_work` - My Initial Assessment**

Alright, so the task at hand is to understand the inner workings of `vsock_loopback.c`, specifically focusing on how `pkt_work` is utilized. Given my expertise, I'm already anticipating certain aspects. I'm guessing `vsock_loopback.c` probably handles a loopback mechanism for vsock communication, which implies it's intended for testing or local interactions, I suspect `pkt_work` is likely the core work queue callback that deals with packets or data in the loopback scenario. So, I need to understand what that callback does, because that's where the heart of the loopback data flow will be. I need to figure out the chain of function calls from within `pkt_work`. I'll be looking for clues on packet processing, data transfer, and error handling. I'll need to follow the execution path closely, and I'll be sure to cross-reference my understanding with the vsock specification to ensure proper operation.



12/2 2026/10/10 14:34 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_loopback.c",
  "FirstLine": 81,
  "LineCount": 80
}

Results:
{
  "Contents": "  81:\t\t\t.stream_dequeue           = virtio_transport_stream_dequeue,\n  82:\t\t\t.stream_enqueue           = virtio_transport_stream_enqueue,\n  83:\t\t\t.stream_has_data          = virtio_transport_stream_has_data,\n  84:\t\t\t.stream_has_space         = virtio_transport_stream_has_space,\n  85:\t\t\t.stream_rcvhiwat          = virtio_transport_stream_rcvhiwat,\n  86:\t\t\t.stream_is_active         = virtio_transport_stream_is_active,\n  87:\t\t\t.stream_allow             = vsock_loopback_stream_allow,\n  88:\t\n  89:\t\t\t.seqpacket_dequeue        = virtio_transport_seqpacket_dequeue,\n  90:\t\t\t.seqpacket_enqueue        = virtio_transport_seqpacket_enqueue,\n  91:\t\t\t.seqpacket_allow          = vsock_loopback_seqpacket_allow,\n  92:\t\t\t.seqpacket_has_data       = virtio_transport_seqpacket_has_data,\n  93:\t\n  94:\t\t\t.msgzerocopy_allow        = vsock_loopback_msgzerocopy_allow,\n  95:\t\n  96:\t\t\t.notify_poll_in           = virtio_transport_notify_poll_in,\n  97:\t\t\t.notify_poll_out          = virtio_transport_notify_poll_out,\n  98:\t\t\t.notify_recv_init         = virtio_transport_notify_recv_init,\n  99:\t\t\t.notify_recv_pre_block    = virtio_transport_notify_recv_pre_block,\n 100:\t\t\t.notify_recv_pre_dequeue  = virtio_transport_notify_recv_pre_dequeue,\n 101:\t\t\t.notify_recv_post_dequeue = virtio_transport_notify_recv_post_dequeue,\n 102:\t\t\t.notify_send_init         = virtio_transport_notify_send_init,\n 103:\t\t\t.notify_send_pre_block    = virtio_transport_notify_send_pre_block,\n 104:\t\t\t.notify_send_pre_enqueue  = virtio_transport_notify_send_pre_enqueue,\n 105:\t\t\t.notify_send_post_enqueue = virtio_transport_notify_send_post_enqueue,\n 106:\t\t\t.notify_buffer_size       = virtio_transport_notify_buffer_size,\n 107:\t\t\t.notify_set_rcvlowat      = virtio_transport_notify_set_rcvlowat,\n 108:\t\n 109:\t\t\t.unsent_bytes             = virtio_transport_unsent_bytes,\n 110:\t\n 111:\t\t\t.read_skb = virtio_transport_read_skb,\n 112:\t\t},\n 113:\t\n 114:\t\t.send_pkt = vsock_loopback_send_pkt,\n 115:\t};\n 116:\t\n 117:\tstatic bool\n 118:\tvsock_loopback_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)\n 119:\t{\n 120:\t\treturn vsock_net_mode_global(vsk);\n 121:\t}\n 122:\t\n 123:\tstatic void vsock_loopback_work(struct work_struct *work)\n 124:\t{\n 125:\t\tstruct vsock_loopback *vsock =\n 126:\t\t\tcontainer_of(work, struct vsock_loopback, pkt_work);\n 127:\t\tstruct sk_buff_head pkts;\n 128:\t\tstruct sk_buff *skb;\n 129:\t\n 130:\t\tskb_queue_head_init(\u0026pkts);\n 131:\t\n 132:\t\tspin_lock_bh(\u0026vsock-\u003epkt_queue.lock);\n 133:\t\tskb_queue_splice_init(\u0026vsock-\u003epkt_queue, \u0026pkts);\n 134:\t\tspin_unlock_bh(\u0026vsock-\u003epkt_queue.lock);\n 135:\t\n 136:\t\twhile ((skb = __skb_dequeue(\u0026pkts))) {\n 137:\t\t\t/* Decrement the bytes_unsent counter without deallocating skb\n 138:\t\t\t * It is freed by the receiver.\n 139:\t\t\t */\n 140:\t\t\tvirtio_transport_consume_skb_sent(skb, false);\n 141:\t\t\tvirtio_transport_deliver_tap_pkt(skb);\n 142:\t\t\tvirtio_transport_recv_pkt(\u0026loopback_transport, skb,\n 143:\t\t\t\t\t\t  sock_net(skb-\u003esk));\n 144:\t\t}\n 145:\t}\n 146:\t\n 147:\tstatic int __init vsock_loopback_init(void)\n 148:\t{\n 149:\t\tstruct vsock_loopback *vsock = \u0026the_vsock_loopback;\n 150:\t\tint ret;\n 151:\t\n 152:\t\tvsock-\u003eworkqueue = alloc_workqueue(\"vsock-loopback\", WQ_PERCPU, 0);\n 153:\t\tif (!vsock-\u003eworkqueue)\n 154:\t\t\treturn -ENOMEM;\n 155:\t\n 156:\t\tskb_queue_head_init(\u0026vsock-\u003epkt_queue);\n 157:\t\tINIT_WORK(\u0026vsock-\u003epkt_work, vsock_loopback_work);\n 158:\t\n 159:\t\tret = vsock_core_register(\u0026loopback_transport.transport,\n 160:\t\t\t\t\t  VSOCK_TRANSPORT_F_LOCAL);\n"
}

13/2 2026/10/10 14:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19439
							output: 41
							thoughts: 2146
						

Thoughts:
**Analysis of Potential Fuzzing Targets in Virtio-VSOCK**

Okay, so here's the breakdown of what I'm seeing.  `vsock_loopback_work` calls `virtio_transport_recv_pkt`, which has undergone a significant refactoring and now uses `virtio_transport_recv_pkt_locked`. The `virtio_transport.c` file is key here because it now calls `virtio_transport_recv_pkt_batch`, which is all about batching, and that immediately gets my attention as an area ripe for fuzzing. We know virtio-vsock is actively tested in QEMU via syzkaller, which adds further weight to its importance. Also, vsock loopback is available in any kernel with `CONFIG_VSOCKETS_LOOPBACK`, which is useful for setting up a test environment.

The BPF sockmap integration has also been modified. Specifically, `vsock_rx_callback_kick(sk)` is now called in `net/vmw_vsock/vsock_bpf.c`, and in `af_vsock.c` we have added functions like `vsock_rx_cb_is_native`, `vsock_rx_callback_handoff`, `vsock_rx_callback_work`, which are new elements in the data path.  We have socket structures (`struct vsock_sock`) with callbacks such as `default_data_ready`, `default_write_space`, and `rx_cb_work` which all contribute.

This all touches the core networking stack: the AF_VSOCK socket layer, and the virtio socket transport (`virtio_transport_common.c`, `virtio_transport.c`, `af_vsock.c`, `vsock_bpf.c`).  This is definitely reachable executable code and there's a lot of potential here.

We can conclude that the `WorthFuzzing` flag is *absolutely* true.  The changes introduce complex concurrency: delayed work, socket locking, packet batching across multiple receive iterations without dropping the socket lock, deferred callbacks (`data_ready`, `write_space`), and handoff logic when sockmap/BPF are attached/detached or custom callbacks set, all of which should be excellent fuzzing targets.

Now for a more detailed analysis:

1.  **WorthFuzzing**: Confirmed - true.
2.  **Focus Symbols**: Let's break down the introduced or modified functions, looking at the patch in detail:
    *   In `net/vmw_vsock/af_vsock.c`: `vsock_rx_callback_kick`, `vsock_rx_cb_is_native`, `vsock_rx_callback_handoff`, `vsock_rx_callback_work` (static), and `vsock_rx_callback_queue` (static).
    *   In `net/vmw_vsock/virtio_transport.c`: `virtio_transport_rx_work` (modified for batching).
    *   In `net/vmw_vsock/virtio_transport_common.c`: `virtio_transport_rx_skb_has_headroom` (static), `virtio_transport_inc_rx_pkt` (modified), `virtio_transport_recv_connected` (modified with data\_ready\_pending), `virtio_transport_recv_pkt_batchable` (static), `virtio_transport_recv_pkt_locked` (static), `virtio_transport_recv_pkt` (modified/refactored), `virtio_transport_rx_batch_finish` (new exported), and `virtio_transport_recv_pkt_batch` (new exported).
    *   In `net/vmw_vsock/vsock_bpf.c`: `vsock_bpf_update_proto` (modified).

    We should examine the exported/non-static symbols:
    *   `virtio_transport_recv_pkt_batch`
    *   `virtio_transport_rx_batch_finish`
    *   `vsock_rx_callback_handoff`
    *   `vsock_rx_cb_is_native`
    *   `virtio_transport_recv_pkt`
    *   `virtio_transport_rx_work`
    *   `vsock_bpf_update_proto`

    Are these good focus symbols? Definitely. We will check if they are kallsyms or visible in vmlinux. Looking at the function definitions, we can determine the following:
    *   `virtio_transport_rx_batch_finish`


14/2 2026/10/10 14:34 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_bpf.c",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0\n   2:\t/* Copyright (c) 2022 Bobby Eshleman \u003cbobby.eshleman@bytedance.com\u003e\n   3:\t *\n   4:\t * Based off of net/unix/unix_bpf.c\n   5:\t */\n   6:\t\n   7:\t#include \u003clinux/bpf.h\u003e\n   8:\t#include \u003clinux/module.h\u003e\n   9:\t#include \u003clinux/skmsg.h\u003e\n  10:\t#include \u003clinux/socket.h\u003e\n  11:\t#include \u003clinux/wait.h\u003e\n  12:\t#include \u003cnet/af_vsock.h\u003e\n  13:\t#include \u003cnet/sock.h\u003e\n  14:\t\n  15:\t#define vsock_sk_has_data(__sk, __psock)\t\t\t\t\\\n  16:\t\t\t({\t!skb_queue_empty(\u0026(__sk)-\u003esk_receive_queue) ||\t\\\n  17:\t\t\t\t!skb_queue_empty(\u0026(__psock)-\u003eingress_skb) ||\t\\\n  18:\t\t\t\t!list_empty(\u0026(__psock)-\u003eingress_msg);\t\t\\\n  19:\t\t\t})\n  20:\t\n  21:\tstatic struct proto *vsock_prot_saved __read_mostly;\n  22:\tstatic DEFINE_SPINLOCK(vsock_prot_lock);\n  23:\tstatic struct proto vsock_bpf_prot;\n  24:\t\n  25:\tstatic bool vsock_has_data(struct sock *sk, struct sk_psock *psock)\n  26:\t{\n  27:\t\tstruct vsock_sock *vsk = vsock_sk(sk);\n  28:\t\ts64 ret;\n  29:\t\n  30:\t\tret = vsock_connectible_has_data(vsk);\n  31:\t\tif (ret \u003e 0)\n  32:\t\t\treturn true;\n  33:\t\n  34:\t\treturn vsock_sk_has_data(sk, psock);\n  35:\t}\n  36:\t\n  37:\tstatic bool vsock_msg_wait_data(struct sock *sk, struct sk_psock *psock, long timeo)\n  38:\t{\n  39:\t\tbool ret;\n  40:\t\n"
}

15/2 2026/10/10 14:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23254
							output: 32
							thoughts: 21
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if I'm thinking these things myself:

**Investigating the `vsock_bpf_update_proto` Function**

Alright, time to dive into this `vsock_bpf_update_proto` function. I need to understand how it's being used within the vsock subsystem. My initial thought is to trace its registration and calls. It’s critical to understand the control flow, so I'm thinking: How is this hook or function getting hooked up into the BPF program flow? Is it being dynamically registered somewhere, and what are the specific contexts? Are there any obvious places like `init` functions, or maybe probe points where it gets attached? I need to track the path from its definition to its execution, which will probably involve looking at either kernel code or BPF program loading mechanisms. This will tell me *exactly* how the vsock protocol's behaviour is being influenced by BPF, or if it's the other way around. I'll need to consider the different potential entry points to the function to see the different pathways this takes. I want to build a clear picture of how this ties into the larger vsock BPF architecture.



16/2 2026/10/10 14:34 tool grepper
0m Args:
{
  "Expression": "vsock_bpf_update_proto",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=182=struct proto vsock_proto = {\n--\nnet/vmw_vsock/af_vsock.c-187-#ifdef CONFIG_BPF_SYSCALL\nnet/vmw_vsock/af_vsock.c:188:\t.psock_update_sk_prot = vsock_bpf_update_proto,\nnet/vmw_vsock/af_vsock.c-189-#endif\n--\nnet/vmw_vsock/vsock_bpf.c=134=static void vsock_bpf_check_needs_rebuild(struct proto *ops)\n--\nnet/vmw_vsock/vsock_bpf.c-149-\nnet/vmw_vsock/vsock_bpf.c:150:int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)\nnet/vmw_vsock/vsock_bpf.c-151-{\n"
}

17/2 2026/10/10 14:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23836
							output: 275
							thoughts: 655
						

Thoughts:
**My Analysis of the AF_VSOCK and Virtio-VSOCK Changes**

Okay, let's break this down. First, I need to confirm the configurations required to enable this functionality. I see a connection between the BPF system call enablement and some changes in `net/vmw_vsock/af_vsock.c`, specifically the `vsock_bpf_update_proto` function, which is guarded by `#ifdef CONFIG_BPF_SYSCALL`. This points me toward needing `BPF_SYSCALL` enabled.

Next, I'm examining the virtio-vsock side. `VIRTIO_VSOCKETS` builds `virtio_transport.o`, which includes the RX worker and new batching logic. `VIRTIO_VSOCKETS_COMMON` includes `virtio_transport_recv_pkt_batch` and `virtio_transport_rx_batch_finish`.  `VSOCKETS` itself builds `af_vsock.o`, so that's a key piece.  Finally, `VSOCKETS_LOOPBACK` seems relevant as well.

Therefore, the initial set of configs I need to consider are: `VSOCKETS`, `VIRTIO_VSOCKETS`, `VSOCKETS_LOOPBACK`, and `BPF_SYSCALL`. I remember the dependencies, and since `VIRTIO_VSOCKETS` depends on `VSOCKETS && VIRTIO`, `VIRTIO` should also be included in our list.

I'm now zeroing in on the crucial functions affected to build my FocusSymbols list.  I'm looking for specific functions from the modified code, avoiding ubiquitous or general-purpose functions.  Based on my assessment of the source, the functions that are good candidates are: `virtio_transport_recv_pkt_batch`, `virtio_transport_rx_batch_finish`, `virtio_transport_recv_pkt`, `vsock_rx_callback_handoff`, `vsock_rx_cb_is_native` and `vsock_bpf_update_proto`. I confirm these are all specific to AF_VSOCK and virtio-vsock; they are not generic enough to be omitted.

Finally, I’m doing a sanity check on my reasoning. The patch introduces RX packet batching for virtio vsock, using `virtio_transport_recv_pkt_batch` and `virtio_transport_rx_batch_finish`.  It holds the socket lock across multiple packets and defers data_ready/write_space callbacks.  It also handles handoff and synchronization with BPF sockmap/psock protocol replacements, specifically `vsock_rx_callback_handoff`, uses a delayed work queue (`rx_cb_work`), and kicks callbacks on protocol updates. This implies new locking, state transitions, callback deferral, and workqueue interactions in networking code. Since this involves all those things, and I can identify the specific functions, I'm confident that this is worth fuzzing. I'm ready to proceed.



18/2 2026/10/10 14:35 tool set-results
0m Args:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VIRTIO_VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "BPF_SYSCALL",
    "VIRTIO"
  ],
  "FocusSymbols": [
    "virtio_transport_recv_pkt_batch",
    "virtio_transport_rx_batch_finish",
    "virtio_transport_recv_pkt",
    "vsock_rx_callback_handoff",
    "vsock_rx_cb_is_native",
    "vsock_bpf_update_proto"
  ],
  "Reasoning": "The patch introduces an RX packet batching mechanism for virtio vsock, allowing multiple packets to be processed under a single socket lock and deferring data_ready and write_space callbacks. It also adds callback handoff handling via delayed work when custom callbacks or BPF sockmap protocol replacements are active. These changes alter socket locking, state machine transitions, and callback synchronization across AF_VSOCK and virtio transports, which are fully reachable in standard virtualized environments (e.g., QEMU virtio-vsock and vsock loopback). Therefore, fuzzing is warranted to test for potential regressions, concurrency races, or state mismatches.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VIRTIO_VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "BPF_SYSCALL",
    "VIRTIO"
  ],
  "FocusSymbols": [
    "virtio_transport_recv_pkt_batch",
    "virtio_transport_rx_batch_finish",
    "virtio_transport_recv_pkt",
    "vsock_rx_callback_handoff",
    "vsock_rx_cb_is_native",
    "vsock_bpf_update_proto"
  ],
  "Reasoning": "The patch introduces an RX packet batching mechanism for virtio vsock, allowing multiple packets to be processed under a single socket lock and deferring data_ready and write_space callbacks. It also adds callback handoff handling via delayed work when custom callbacks or BPF sockmap protocol replacements are active. These changes alter socket locking, state machine transitions, and callback synchronization across AF_VSOCK and virtio transports, which are fully reachable in standard virtualized environments (e.g., QEMU virtio-vsock and vsock loopback). Therefore, fuzzing is warranted to test for potential regressions, concurrency races, or state mismatches.",
  "WorthFuzzing": true
}

19/1 2026/10/10 14:35 action If
3m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/10/10 14:35 agent kmsan-evaluator
3m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series implements receive packet batching and deferred callback handling for virtio-vsock to optimize locking overhead across sequences of STREAM/RW packets.\n\nEvaluation of KMSAN vs KASAN/LOCKDEP applicability:\n1. Memory initialization: The new stack-allocated batch tracking structure (`struct virtio_transport_rx_batch`) is zero-initialized (`batch = {}`). New socket fields in `struct vsock_sock` (`default_data_ready`, `default_write_space`, `rx_cb_work`, `rx_cb_retried`) are allocated via `sk_alloc` (which zeroes memory) and explicitly initialized in `__vsock_create`. Address structures (`src`, `dst`) are initialized via `vsock_addr_init`, which zeroes the entire `struct sockaddr_vm` via `memset`. Context structures and boolean flags are fully assigned before being read or used in conditional branches.\n2. Info-leaks / User-space copies: The changes do not introduce or modify any data transfers to user space (`copy_to_user`, ioctl, netlink, or socket options).\n3. Buffer bounds / uninitialized buffer reads: The batching logic does not alter buffer slicing or payload boundary checks in a way that could expose uninitialized memory.\n4. Bug profile: The primary failure modes of this patch involve socket reference counting, socket/callback lock ordering, race conditions with BPF/sockmap callback changes, or use-after-free conditions on socket or sk_buff lifetimes. These are comprehensively addressed by KASAN, LOCKDEP, and standard kernel debugging facilities, not KMSAN.\n\nTherefore, running a dedicated KMSAN session is not warranted.",
  "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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 976e9947786434b4e7066947337110fe5ba02405
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Oct 10 14:33:15 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057e..21a3460b940f2 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -282,6 +282,22 @@ void virtio_transport_destruct(struct vsock_sock *vsk);
 
 void virtio_transport_recv_pkt(struct virtio_transport *t,
 			       struct sk_buff *skb, struct net *net);
+
+struct virtio_transport_rx_batch {
+	struct sock *sk;
+	unsigned int pkts;
+	size_t bytes;
+	struct net *net;
+	struct sockaddr_vm src;
+	struct sockaddr_vm dst;
+	bool write_space_pending;
+	bool data_ready_pending;
+};
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+				     struct sk_buff *skb, struct net *net,
+				     struct virtio_transport_rx_batch *batch);
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch);
 void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);
 u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
 void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6b..86d6e6ba3d9df 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,6 +63,11 @@ struct vsock_sock {
 	u32 peer_shutdown;
 	bool sent_request;
 	bool ignore_connecting_rst;
+	/* Initial callbacks, used to identify replacements. */
+	void (*default_data_ready)(struct sock *sk);
+	void (*default_write_space)(struct sock *sk);
+	struct delayed_work rx_cb_work;
+	bool rx_cb_retried;
 
 	/* Protected by lock_sock(sk) */
 	u64 buffer_size;
@@ -78,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk);
 s64 vsock_stream_has_space(struct vsock_sock *vsk);
 struct sock *vsock_create_connected(struct sock *parent);
 void vsock_data_ready(struct sock *sk);
+bool vsock_rx_cb_is_native(struct sock *sk);
+void vsock_rx_callback_kick(struct sock *sk);
+void vsock_rx_callback_handoff(struct sock *sk);
 
 /**** TRANSPORT ****/
 
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44cee74519555..df6faee0bd37a 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 	return retval;
 }
 
+#define VSOCK_RX_CB_HANDOFF_MAX 64
+
+static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+
+	sock_hold(sk);
+	if (!schedule_delayed_work(&vsk->rx_cb_work, delay))
+		sock_put(sk);
+}
+
+void vsock_rx_callback_kick(struct sock *sk)
+{
+	vsock_rx_callback_queue(sk, 0);
+}
+
+/* Caller holds sk_callback_lock. */
+bool vsock_rx_cb_is_native(struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+
+	return READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+	       READ_ONCE(sk->sk_data_ready) == vsk->default_data_ready &&
+	       READ_ONCE(sk->sk_write_space) == vsk->default_write_space;
+}
+EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);
+
+/* Caller holds lock_sock(). */
+void vsock_rx_callback_handoff(struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*write_space)(struct sock *sk);
+	void (*data_ready)(struct sock *sk);
+	bool wrote = false;
+	s64 before, after;
+	int i;
+
+	if (sock_flag(sk, SOCK_DEAD) || !vsk->transport)
+		return;
+
+	read_lock_bh(&sk->sk_callback_lock);
+	data_ready = READ_ONCE(sk->sk_data_ready);
+	if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator &&
+	    data_ready == vsk->default_data_ready) {
+		read_unlock_bh(&sk->sk_callback_lock);
+		/*
+		 * start_verdict publishes sk_data_ready after the proto swap.
+		 * Retry once before treating this as a stable non-RX setup.
+		 */
+		if (!vsk->rx_cb_retried) {
+			vsk->rx_cb_retried = true;
+			vsock_rx_callback_queue(sk, 1);
+			return;
+		}
+		vsk->rx_cb_retried = false;
+		data_ready(sk);
+		return;
+	}
+	read_unlock_bh(&sk->sk_callback_lock);
+	vsk->rx_cb_retried = false;
+
+	for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) {
+		read_lock_bh(&sk->sk_callback_lock);
+		data_ready = READ_ONCE(sk->sk_data_ready);
+		write_space = READ_ONCE(sk->sk_write_space);
+		read_unlock_bh(&sk->sk_callback_lock);
+
+		if (!wrote && write_space != vsk->default_write_space) {
+			write_space(sk);
+			wrote = true;
+		}
+		if (data_ready == vsk->default_data_ready) {
+			data_ready(sk);
+			break;
+		}
+		before = vsock_stream_has_data(vsk);
+		if (before <= 0)
+			break;
+		data_ready(sk);
+		after = vsock_stream_has_data(vsk);
+		if (after >= before)
+			break;
+		if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0)
+			vsock_rx_callback_queue(sk, 0);
+	}
+}
+EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff);
+
+static void vsock_rx_callback_work(struct work_struct *work)
+{
+	struct vsock_sock *vsk = container_of(work, struct vsock_sock,
+					      rx_cb_work.work);
+	struct sock *sk = sk_vsock(vsk);
+
+	lock_sock(sk);
+	if (!sock_flag(sk, SOCK_DEAD))
+		vsock_rx_callback_handoff(sk);
+	release_sock(sk);
+	sock_put(sk);
+}
+
 static void vsock_connect_timeout(struct work_struct *work);
 
 static struct sock *__vsock_create(struct net *net,
@@ -958,6 +1059,8 @@ static struct sock *__vsock_create(struct net *net,
 		sk->sk_type = type;
 
 	vsk = vsock_sk(sk);
+	vsk->default_data_ready = sk->sk_data_ready;
+	vsk->default_write_space = sk->sk_write_space;
 	vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
 	vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
 
@@ -975,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net,
 	WRITE_ONCE(vsk->peer_shutdown, 0);
 	INIT_DELAYED_WORK(&vsk->connect_work, vsock_connect_timeout);
 	INIT_DELAYED_WORK(&vsk->pending_work, vsock_pending_work);
+	INIT_DELAYED_WORK(&vsk->rx_cb_work, vsock_rx_callback_work);
 
 	psk = parent ? vsock_sk(parent) : NULL;
 	if (parent) {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa5..ef076732158d4 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
 	return seqpacket_allow;
 }
 
+/*
+ * Keep a bounded run of packets for one socket under a single socket lock.
+ * Limit packet count and payload size to bound the work done while locked.
+ */
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS	64
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES	(64 * 1024)
+
 static void virtio_transport_rx_work(struct work_struct *work)
 {
+	struct virtio_transport_rx_batch batch = {};
 	struct virtio_vsock *vsock =
 		container_of(work, struct virtio_vsock, rx_work);
 	struct virtqueue *vq;
@@ -666,6 +674,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			/* Drop short/long packets */
 			if (unlikely(len < sizeof(*hdr) ||
 				     len > virtio_vsock_skb_len(skb))) {
+				virtio_transport_rx_batch_finish(&batch);
 				kfree_skb(skb);
 				continue;
 			}
@@ -673,6 +682,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			hdr = virtio_vsock_hdr(skb);
 			payload_len = le32_to_cpu(hdr->len);
 			if (unlikely(payload_len > len - sizeof(*hdr))) {
+				virtio_transport_rx_batch_finish(&batch);
 				kfree_skb(skb);
 				continue;
 			}
@@ -680,16 +690,33 @@ static void virtio_transport_rx_work(struct work_struct *work)
 			if (payload_len)
 				virtio_vsock_skb_put(skb, payload_len);
 
+			if (batch.sk &&
+			    payload_len > VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES -
+					  batch.bytes) {
+				virtio_transport_rx_batch_finish(&batch);
+			}
+
 			virtio_transport_deliver_tap_pkt(skb);
 
 			/* Force virtio-transport into global mode since it
 			 * does not yet support local-mode namespacing.
 			 */
-			virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+			virtio_transport_recv_pkt_batch(&virtio_transport, skb, NULL,
+							&batch);
+
+			if (batch.sk) {
+				batch.pkts++;
+				batch.bytes += payload_len;
+				if (batch.pkts >= VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS ||
+				    batch.bytes >= VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES) {
+					virtio_transport_rx_batch_finish(&batch);
+				}
+			}
 		}
 	} while (!virtqueue_enable_cb(vq));
 
 out:
+	virtio_transport_rx_batch_finish(&batch);
 	if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
 		virtio_vsock_rx_fill(vsock);
 out_nofill:
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f225f53ed4bab..6b96d8ac0d598 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
 	skb_queue_splice(&new_queue, &vvs->rx_queue);
 }
 
+static bool
+virtio_transport_rx_skb_has_headroom(struct virtio_vsock_sock *vvs)
+{
+	return (u64)(skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0) <=
+		vvs->buf_alloc;
+}
+
 static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
 					u32 len)
 {
-	u64 skb_overhead = (skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0);
 
 	/* Allow at most buf_alloc * 2 total budget (payload + overhead),
 	 * similar to how SO_RCVBUF is doubled to reserve space for sk_buff
@@ -588,7 +594,7 @@ static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
 	 * queue growth.
 	 */
 	if ((u64)vvs->buf_used + len > vvs->buf_alloc ||
-	    skb_overhead > vvs->buf_alloc)
+	    !virtio_transport_rx_skb_has_headroom(vvs))
 		return false;
 
 	vvs->rx_bytes += len;
@@ -1583,10 +1589,12 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,
 
 static int
 virtio_transport_recv_connected(struct sock *sk,
-				struct sk_buff *skb)
+				struct sk_buff *skb,
+				bool *data_ready_pending)
 {
 	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
 	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*data_ready)(struct sock *sk);
 	int err = 0;
 
 	switch (le16_to_cpu(hdr->op)) {
@@ -1602,7 +1610,23 @@ virtio_transport_recv_connected(struct sock *sk,
 			vsock_remove_sock(vsk);
 			break;
 		}
-		vsock_data_ready(sk);
+		if (!data_ready_pending) {
+			vsock_data_ready(sk);
+		} else {
+			data_ready = READ_ONCE(sk->sk_data_ready);
+			if (data_ready == vsk->default_data_ready) {
+				if (!*data_ready_pending &&
+				    (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				     sock_flag(sk, SOCK_DONE)))
+					*data_ready_pending = true;
+			} else {
+				/* Use the callback seen for this packet. */
+				*data_ready_pending = false;
+				if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				    sock_flag(sk, SOCK_DONE))
+					data_ready(sk);
+			}
+		}
 		return err;
 	case VIRTIO_VSOCK_OP_CREDIT_REQUEST:
 		virtio_transport_send_credit_update(vsk);
@@ -1774,84 +1798,133 @@ static bool virtio_transport_valid_type(u16 type)
 	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);
 }
 
-/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
- * lock.
- */
-void virtio_transport_recv_pkt(struct virtio_transport *t,
-			       struct sk_buff *skb, struct net *net)
+static void
+virtio_transport_recv_pkt_init_addrs(struct sk_buff *skb,
+				     struct sockaddr_vm *src,
+				     struct sockaddr_vm *dst)
 {
 	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
-	struct sockaddr_vm src, dst;
-	struct vsock_sock *vsk;
-	struct sock *sk;
-	bool space_available;
 
-	vsock_addr_init(&src, le64_to_cpu(hdr->src_cid),
+	vsock_addr_init(src, le64_to_cpu(hdr->src_cid),
 			le32_to_cpu(hdr->src_port));
-	vsock_addr_init(&dst, le64_to_cpu(hdr->dst_cid),
+	vsock_addr_init(dst, le64_to_cpu(hdr->dst_cid),
 			le32_to_cpu(hdr->dst_port));
+}
 
-	trace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,
-					dst.svm_cid, dst.svm_port,
+static void
+virtio_transport_trace_recv_pkt(struct sk_buff *skb,
+				const struct sockaddr_vm *src,
+				const struct sockaddr_vm *dst)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+
+	trace_virtio_transport_recv_pkt(src->svm_cid, src->svm_port,
+					dst->svm_cid, dst->svm_port,
 					le32_to_cpu(hdr->len),
 					le16_to_cpu(hdr->type),
 					le16_to_cpu(hdr->op),
 					le32_to_cpu(hdr->flags),
 					le32_to_cpu(hdr->buf_alloc),
 					le32_to_cpu(hdr->fwd_cnt));
+}
 
-	if (!virtio_transport_valid_type(le16_to_cpu(hdr->type))) {
-		(void)virtio_transport_reset_no_sock(t, skb, net);
-		goto free_pkt;
-	}
+static struct sock *
+virtio_transport_recv_pkt_find_socket(struct sk_buff *skb,
+				      struct sockaddr_vm *src,
+				      struct sockaddr_vm *dst,
+				      struct net *net)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+	struct sock *sk;
 
-	/* The socket must be in connected or bound table
-	 * otherwise send reset back
-	 */
-	sk = vsock_find_connected_socket_net(&src, &dst, net);
-	if (!sk) {
-		sk = vsock_find_bound_socket_net(&dst, net);
-		if (!sk) {
-			(void)virtio_transport_reset_no_sock(t, skb, net);
-			goto free_pkt;
-		}
-	}
+	if (!virtio_transport_valid_type(le16_to_cpu(hdr->type)))
+		return NULL;
+
+	sk = vsock_find_connected_socket_net(src, dst, net);
+	if (!sk)
+		sk = vsock_find_bound_socket_net(dst, net);
+	if (!sk)
+		return NULL;
 
 	if (virtio_transport_get_type(sk) != le16_to_cpu(hdr->type)) {
-		(void)virtio_transport_reset_no_sock(t, skb, net);
 		sock_put(sk);
-		goto free_pkt;
+		return NULL;
 	}
 
-	if (!skb_set_owner_sk_safe(skb, sk)) {
-		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
-		goto free_pkt;
-	}
+	return sk;
+}
 
-	vsk = vsock_sk(sk);
+struct virtio_transport_rx_pkt_ctx {
+	struct net *net;
+	const struct sockaddr_vm *src;
+	const struct sockaddr_vm *dst;
+	bool *batchable;
+	struct virtio_transport_rx_batch *batch;
+	bool defer_data_ready;
+};
 
-	lock_sock(sk);
+static bool
+virtio_transport_recv_pkt_batchable(struct virtio_transport *t,
+				    struct sock *sk)
+{
+	struct vsock_sock *vsk = vsock_sk(sk);
+	struct virtio_vsock_sock *vvs = vsk->trans;
+
+	return sk->sk_state == TCP_ESTABLISHED &&
+	       sk->sk_type == SOCK_STREAM &&
+	       READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+	       !sock_flag(sk, SOCK_DONE) &&
+	       vsk->transport == &t->transport &&
+	       vvs && virtio_transport_rx_skb_has_headroom(vvs);
+}
+
+/*
+ * The caller holds sk's socket lock.  Set @batchable if the socket can remain
+ * locked for another ordinary STREAM/RW packet.  Return true if the caller
+ * must free @skb.
+ */
+static bool
+virtio_transport_recv_pkt_locked(struct virtio_transport *t,
+				 struct sk_buff *skb, struct sock *sk,
+				 const struct virtio_transport_rx_pkt_ctx *ctx)
+{
+	const struct sockaddr_vm *src = ctx->src;
+	const struct sockaddr_vm *dst = ctx->dst;
+	struct vsock_sock *vsk = vsock_sk(sk);
+	void (*write_space)(struct sock *sk);
+	struct net *net = ctx->net;
+	bool space_available;
 
-	/* Check if sk has been closed or assigned to another transport before
-	 * lock_sock (note: listener sockets are not assigned to any transport)
+	if (ctx->batchable)
+		*ctx->batchable = false;
+
+	/* Check after acquiring the socket lock. Listener sockets accept packets
+	 * from any source and are not assigned to a transport.
 	 */
 	if (sock_flag(sk, SOCK_DONE) ||
 	    (sk->sk_state != TCP_LISTEN &&
-	     !vsock_check_source(vsk, &t->transport, &src))) {
+	     !vsock_check_source(vsk, &t->transport, src))) {
 		(void)virtio_transport_reset_no_sock(t, skb, net);
-		release_sock(sk);
-		sock_put(sk);
-		goto free_pkt;
+		return true;
 	}
 
 	space_available = virtio_transport_space_update(sk, skb);
 
 	/* Update CID in case it has changed after a transport reset event */
 	if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
-		vsk->local_addr.svm_cid = dst.svm_cid;
-
-	if (space_available)
-		sk->sk_write_space(sk);
+		vsk->local_addr.svm_cid = dst->svm_cid;
+
+	if (space_available) {
+		write_space = READ_ONCE(sk->sk_write_space);
+		if (ctx->batch &&
+		    write_space == vsk->default_write_space &&
+		    virtio_transport_recv_pkt_batchable(t, sk)) {
+			ctx->batch->write_space_pending = true;
+		} else {
+			/* Use the callback seen for this packet. */
+			write_space(sk);
+		}
+	}
 
 	switch (sk->sk_state) {
 	case TCP_LISTEN:
@@ -1863,7 +1936,9 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		kfree_skb(skb);
 		break;
 	case TCP_ESTABLISHED:
-		virtio_transport_recv_connected(sk, skb);
+		virtio_transport_recv_connected(sk, skb,
+						ctx->defer_data_ready && ctx->batch ?
+						&ctx->batch->data_ready_pending : NULL);
 		break;
 	case TCP_CLOSING:
 		virtio_transport_recv_disconnecting(sk, skb);
@@ -1875,12 +1950,52 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		break;
 	}
 
+	if (ctx->batchable)
+		*ctx->batchable = virtio_transport_recv_pkt_batchable(t, sk);
+
+	return false;
+}
+
+/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
+ * lock.
+ */
+void virtio_transport_recv_pkt(struct virtio_transport *t,
+			       struct sk_buff *skb, struct net *net)
+{
+	struct virtio_transport_rx_pkt_ctx ctx;
+	struct sockaddr_vm src, dst;
+	struct sock *sk;
+	bool free_pkt;
+
+	virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+	virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+	sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+	if (!sk) {
+		(void)virtio_transport_reset_no_sock(t, skb, net);
+		goto free_pkt;
+	}
+
+	if (!skb_set_owner_sk_safe(skb, sk)) {
+		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+		goto free_pkt;
+	}
+
+	lock_sock(sk);
+	ctx = (struct virtio_transport_rx_pkt_ctx) {
+		.net = net,
+		.src = &src,
+		.dst = &dst,
+	};
+	free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
 	release_sock(sk);
 
 	/* Release refcnt obtained when we fetched this socket out of the
 	 * bound or connected list.
 	 */
 	sock_put(sk);
+	if (free_pkt)
+		kfree_skb(skb);
 	return;
 
 free_pkt:
@@ -1888,6 +2003,178 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 }
 EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
 
+/*
+ * Finish the RX batch.
+ * For a non-empty batch, the caller must hold the socket lock acquired with
+ * lock_sock(). This function releases the lock and the batch's lookup
+ * reference. An empty batch is a no-op.
+ */
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
+{
+	bool write_space_pending = batch->write_space_pending;
+	bool data_ready_pending = batch->data_ready_pending;
+	struct sock *sk = batch->sk;
+	struct vsock_sock *vsk;
+	bool native;
+
+	batch->sk = NULL;
+	batch->pkts = 0;
+	batch->bytes = 0;
+	batch->net = NULL;
+	batch->write_space_pending = false;
+	batch->data_ready_pending = false;
+
+	if (!sk)
+		return;
+
+	vsk = vsock_sk(sk);
+	if (write_space_pending)
+		vsk->default_write_space(sk);
+
+	read_lock_bh(&sk->sk_callback_lock);
+	native = vsock_rx_cb_is_native(sk);
+	read_unlock_bh(&sk->sk_callback_lock);
+
+	if (native) {
+		release_sock(sk);
+		if (data_ready_pending)
+			vsk->default_data_ready(sk);
+		sock_put(sk);
+		return;
+	}
+
+	if (write_space_pending || data_ready_pending)
+		vsock_rx_callback_handoff(sk);
+
+	release_sock(sk);
+	sock_put(sk);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_rx_batch_finish);
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+				     struct sk_buff *skb, struct net *net,
+				     struct virtio_transport_rx_batch *batch)
+{
+	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+	bool batchable, defer_data_ready, start_batch;
+	struct virtio_transport_rx_pkt_ctx ctx;
+	struct sockaddr_vm src, dst;
+	struct sock *sk;
+	bool free_pkt;
+
+	/* Only STREAM/RW packets can share a socket lock. */
+	if (le16_to_cpu(hdr->type) != VIRTIO_VSOCK_TYPE_STREAM ||
+	    le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW) {
+		virtio_transport_rx_batch_finish(batch);
+		virtio_transport_recv_pkt(t, skb, net);
+		return;
+	}
+
+	virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+	virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+	if (batch->sk) {
+		if (batch->net == net &&
+		    vsock_addr_equals_addr(&batch->src, &src) &&
+		    vsock_addr_equals_addr(&batch->dst, &dst) &&
+		    virtio_transport_recv_pkt_batchable(t, batch->sk)) {
+			sk = batch->sk;
+			defer_data_ready = READ_ONCE(sk->sk_data_ready) ==
+					   vsock_sk(sk)->default_data_ready;
+			if (unlikely(batch->data_ready_pending && !defer_data_ready)) {
+				/*
+				 * BPF map updates can replace callbacks while lock_sock()
+				 * remains held. Finish the old notification first.
+				 */
+				virtio_transport_rx_batch_finish(batch);
+				goto lookup;
+			}
+
+			if (!skb_set_owner_sk_safe(skb, sk)) {
+				WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+				virtio_transport_rx_batch_finish(batch);
+				kfree_skb(skb);
+				return;
+			}
+
+			ctx = (struct virtio_transport_rx_pkt_ctx) {
+				.net = net,
+				.src = &src,
+				.dst = &dst,
+				.batchable = &batchable,
+				.batch = batch,
+				.defer_data_ready = defer_data_ready,
+			};
+			free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+
+			if (!batchable)
+				virtio_transport_rx_batch_finish(batch);
+			if (free_pkt)
+				kfree_skb(skb);
+			return;
+		}
+
+		virtio_transport_rx_batch_finish(batch);
+	}
+
+lookup:
+	sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+	if (!sk) {
+		virtio_transport_rx_batch_finish(batch);
+		(void)virtio_transport_reset_no_sock(t, skb, net);
+		kfree_skb(skb);
+		return;
+	}
+
+	if (!skb_set_owner_sk_safe(skb, sk)) {
+		WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+		kfree_skb(skb);
+		return;
+	}
+
+	lock_sock(sk);
+	/*
+	 * Sockmap removal restores the native protocol under sk_callback_lock.
+	 * Serialize this initial eligibility check with that update.
+	 */
+	read_lock_bh(&sk->sk_callback_lock);
+	start_batch = virtio_transport_recv_pkt_batchable(t, sk);
+	read_unlock_bh(&sk->sk_callback_lock);
+
+	if (start_batch)
+		batch->sk = sk;
+	defer_data_ready = start_batch &&
+		READ_ONCE(sk->sk_data_ready) == vsock_sk(sk)->default_data_ready;
+
+	ctx = (struct virtio_transport_rx_pkt_ctx) {
+		.net = net,
+		.src = &src,
+		.dst = &dst,
+		.batchable = start_batch ? &batchable : NULL,
+		.batch = start_batch ? batch : NULL,
+		.defer_data_ready = defer_data_ready,
+	};
+	free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+	if (start_batch && batchable) {
+		/* Keep the lookup reference until the batch is released. */
+		batch->net = net;
+		batch->src = src;
+		batch->dst = dst;
+		return;
+	}
+
+	if (start_batch) {
+		virtio_transport_rx_batch_finish(batch);
+	} else {
+		release_sock(sk);
+		sock_put(sk);
+	}
+
+	if (free_pkt)
+		kfree_skb(skb);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt_batch);
+
 /* Remove skbs found in a queue that have a vsk that matches.
  *
  * Each skb is freed.
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d26486460..7770a6c51dbe5 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 	if (restore) {
 		sk->sk_write_space = psock->saved_write_space;
 		sock_replace_proto(sk, psock->sk_proto);
+		vsock_rx_callback_kick(sk);
 		return 0;
 	}
 
@@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 
 	vsock_bpf_check_needs_rebuild(psock->sk_proto);
 	sock_replace_proto(sk, &vsock_bpf_prot);
+	vsock_rx_callback_kick(sk);
 	return 0;
 }
 

21/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 11794
							output: 21
							thoughts: 991
						

Thoughts:
**KMSAN Fuzzing Evaluation for This Patch Series**

Okay, let's break this down. My goal is to figure out if this patch series warrants a dedicated KMSAN fuzzing session. First, I need to understand what's changing. It looks like we're touching several files related to virtio_vsock: `include/linux/virtio_vsock.h`, `include/net/af_vsock.h`, `net/vmw_vsock/af_vsock.c`, `net/vmw_vsock/virtio_transport.c`, `net/vmw_vsock/virtio_transport_common.c`, and `net/vmw_vsock/vsock_bpf.c`.

I'm seeing new structures and functions declared in `include/linux/virtio_vsock.h`, specifically `struct virtio_transport_rx_batch` and related function prototypes: `virtio_transport_recv_pkt_batch` and `virtio_transport_rx_batch_finish`. This `virtio_transport_rx_batch` seems to be central to this.

In `include/net/af_vsock.h`, I'm noticing modifications to the `vsock_sock` structure and new function prototypes like `vsock_rx_cb_is_native`, `vsock_rx_callback_kick`, and `vsock_rx_callback_handoff`. I see fields like `default_data_ready`, `default_write_space`, and `rx_cb_work` being introduced.

`net/vmw_vsock/af_vsock.c` implements the functions declared in the header file, specifically around receive callbacks. `__vsock_create` is initializing the default callbacks and delayed work.

`net/vmw_vsock/virtio_transport.c` is where the core logic of the batching is being implemented. The `virtio_transport_rx_work` function now instantiates a zero-initialized `struct virtio_transport_rx_batch batch`. The code iterates through packets, batches them, calls `virtio_transport_recv_pkt_batch` and at the exit calls `virtio_transport_rx_batch_finish(&batch)`. That stack allocation might be important later, for understanding exactly how things are cleared.

`net/vmw_vsock/virtio_transport_common.c` refactors the `virtio_transport_recv_pkt` to support batching. It also introduces `virtio_transport_recv_pkt_init_addrs`, which initializes `sockaddr_vm` structures from the packet header. I should make sure to see if that initialization is complete, as uninitialized values could be an issue. Let's see how `vsock_addr_init` is implemented. I'm going to grep for `vsock_addr_init`.



22/3 2026/10/10 14:35 tool grepper
0m Args:
{
  "Expression": "void vsock_addr_init"
}

Results:
{
  "Output": "include/net/vsock_addr.h-12-\ninclude/net/vsock_addr.h:13:void vsock_addr_init(struct sockaddr_vm *addr, u32 cid, u32 port);\ninclude/net/vsock_addr.h-14-int vsock_addr_validate(const struct sockaddr_vm *addr);\n--\nnet/vmw_vsock/vsock_addr.c-13-\nnet/vmw_vsock/vsock_addr.c:14:void vsock_addr_init(struct sockaddr_vm *addr, u32 cid, u32 port)\nnet/vmw_vsock/vsock_addr.c-15-{\n"
}

23/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 13619
							output: 41
							thoughts: 26
						

Thoughts:
Okay, let's dive into this. I'm going to pull up `net/vmw_vsock/vsock_addr.c` and zero in around line 14. I know this code pretty well, so I'm expecting to see the declaration of the `struct sockaddr_vm`, or maybe the beginnings of the address family registration. I'm trying to recall the fundamental structures of the vsock implementation, particularly how the address space is defined and handled within the kernel. It’s important to see how the connection endpoints are identified and whether there are any initial validation steps occurring at this stage. I am interested to see if there is any initial sanity check and how it sets up a context for vsock. The crucial thing for me is to trace how the address itself is constructed, and this looks like a good place to start.



24/3 2026/10/10 14:35 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_addr.c",
  "FirstLine": 12,
  "LineCount": 20
}

Results:
{
  "Contents": "  12:\t#include \u003cnet/vsock_addr.h\u003e\n  13:\t\n  14:\tvoid vsock_addr_init(struct sockaddr_vm *addr, u32 cid, u32 port)\n  15:\t{\n  16:\t\tmemset(addr, 0, sizeof(*addr));\n  17:\t\taddr-\u003esvm_family = AF_VSOCK;\n  18:\t\taddr-\u003esvm_cid = cid;\n  19:\t\taddr-\u003esvm_port = port;\n  20:\t}\n  21:\tEXPORT_SYMBOL_GPL(vsock_addr_init);\n  22:\t\n  23:\tint vsock_addr_validate(const struct sockaddr_vm *addr)\n  24:\t{\n  25:\t\t__u8 svm_valid_flags = VMADDR_FLAG_TO_HOST;\n  26:\t\n  27:\t\tif (!addr)\n  28:\t\t\treturn -EFAULT;\n  29:\t\n  30:\t\tif (addr-\u003esvm_family != AF_VSOCK)\n  31:\t\t\treturn -EAFNOSUPPORT;\n"
}

25/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14152
							output: 22
							thoughts: 201
						

Thoughts:
**Analyzing vsock Address Initialization**

Okay, so `vsock_addr_init` starts with a `memset` which is excellent, meaning both the source (`src`) and destination (`dst`) addresses are guaranteed to be fully zeroed initially. That's a solid foundation. Now, diving deeper into the `virtio_transport_rx_batch` struct, I see that it includes `struct sockaddr_vm src;` and `struct sockaddr_vm dst;` members. This is where the magic happens.

Looking at the initialization process within `virtio_transport_rx_work`, the batch itself is declared as `struct virtio_transport_rx_batch batch = {};`, which, by C's rules, will zero the entire struct. Nice and clean!

Further down the rabbit hole, in `virtio_transport_recv_pkt_batch`, we have `virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);`, which fully initializes those `src` and `dst` addresses. This implies that they are *not* left uninitialized or potentially filled with garbage data. Then, `batch->src = src; batch->dst = dst;` assigns the initialized addresses to the batch's members.

The final sanity check, `vsock_addr_equals_addr(&batch->src, &src)`, is what I really need to focus on. Since `src` and `dst` are properly initialized, assigned, and compared, there shouldn't be any issues. I need to make sure `vsock_addr_equals_addr` does what I expect it to. This is where I need to dig into the definition to verify that everything is working as it should.



26/3 2026/10/10 14:35 tool grepper
0m Args:
{
  "Expression": "vsock_addr_equals_addr"
}

Results:
{
  "Output": "include/net/vsock_addr.h=16=void vsock_addr_unbind(struct sockaddr_vm *addr);\ninclude/net/vsock_addr.h:17:bool vsock_addr_equals_addr(const struct sockaddr_vm *addr,\ninclude/net/vsock_addr.h-18-\t\t\t    const struct sockaddr_vm *other);\n--\nnet/vmw_vsock/af_vsock.c=297=static struct sock *__vsock_find_bound_socket_net(struct sockaddr_vm *addr,\n--\nnet/vmw_vsock/af_vsock.c-304-\nnet/vmw_vsock/af_vsock.c:305:\t\tif (vsock_addr_equals_addr(addr, \u0026vsk-\u003elocal_addr) \u0026\u0026\nnet/vmw_vsock/af_vsock.c-306-\t\t    vsock_net_check_mode(sock_net(sk), net))\n--\nnet/vmw_vsock/af_vsock.c=320=__vsock_find_connected_socket_net(struct sockaddr_vm *src,\n--\nnet/vmw_vsock/af_vsock.c-328-\nnet/vmw_vsock/af_vsock.c:329:\t\tif (vsock_addr_equals_addr(src, \u0026vsk-\u003eremote_addr) \u0026\u0026\nnet/vmw_vsock/af_vsock.c-330-\t\t    dst-\u003esvm_port == vsk-\u003elocal_addr.svm_port \u0026\u0026\n--\nnet/vmw_vsock/virtio_transport_common.c=2054=void virtio_transport_recv_pkt_batch(struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-2077-\t\tif (batch-\u003enet == net \u0026\u0026\nnet/vmw_vsock/virtio_transport_common.c:2078:\t\t    vsock_addr_equals_addr(\u0026batch-\u003esrc, \u0026src) \u0026\u0026\nnet/vmw_vsock/virtio_transport_common.c:2079:\t\t    vsock_addr_equals_addr(\u0026batch-\u003edst, \u0026dst) \u0026\u0026\nnet/vmw_vsock/virtio_transport_common.c-2080-\t\t    virtio_transport_recv_pkt_batchable(t, batch-\u003esk)) {\n--\nnet/vmw_vsock/vmci_transport.c=478=static struct sock *vmci_transport_get_pending(\n--\nnet/vmw_vsock/vmci_transport.c-492-\t\t\t    pending_links) {\nnet/vmw_vsock/vmci_transport.c:493:\t\tif (vsock_addr_equals_addr(\u0026src, \u0026vpending-\u003eremote_addr) \u0026\u0026\nnet/vmw_vsock/vmci_transport.c-494-\t\t    pkt-\u003edst_port == vpending-\u003elocal_addr.svm_port) {\n--\nnet/vmw_vsock/vsock_addr.c=50=EXPORT_SYMBOL_GPL(vsock_addr_unbind);\nnet/vmw_vsock/vsock_addr.c-51-\nnet/vmw_vsock/vsock_addr.c:52:bool vsock_addr_equals_addr(const struct sockaddr_vm *addr,\nnet/vmw_vsock/vsock_addr.c-53-\t\t\t    const struct sockaddr_vm *other)\n--\nnet/vmw_vsock/vsock_addr.c-57-}\nnet/vmw_vsock/vsock_addr.c:58:EXPORT_SYMBOL_GPL(vsock_addr_equals_addr);\nnet/vmw_vsock/vsock_addr.c-59-\n"
}

27/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15685
							output: 41
							thoughts: 33
						

Thoughts:
Okay, let's dive into this. Here's my take on understanding `vsock_addr_equals_addr` in `net/vmw_vsock/vsock_addr.c`:

**Dissecting vsock_addr_equals_addr**

Right, so I need to understand how the vsock address comparison works within the VMware vsock context. Seems like a basic, but potentially critical, function. I’m thinking `net/vmw_vsock/vsock_addr.c` is the right place to be looking, which makes sense given the filename.

My immediate assumption is that this function, `vsock_addr_equals_addr`, would be responsible for checking if two vsock addresses are the same. That's a fundamental operation for any networking stack, obviously. I'm wondering if it's a simple memcmp-style comparison on the underlying structure, or if there's more to it – perhaps handling address families, any alignment issues, or some internal representation specifics that I need to be aware of. The `vmw_vsock` subsystem is likely using a specialized struct, and I'd bet this function is the one that's checking equality between instances of it. Hopefully it’s well-documented! Time to crack open the source code and find out. I need to see what fields it's comparing and if it accounts for any subtle nuances in the vsock addressing scheme.



28/3 2026/10/10 14:35 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_addr.c",
  "FirstLine": 50,
  "LineCount": 15
}

Results:
{
  "Contents": "  50:\tEXPORT_SYMBOL_GPL(vsock_addr_unbind);\n  51:\t\n  52:\tbool vsock_addr_equals_addr(const struct sockaddr_vm *addr,\n  53:\t\t\t\t    const struct sockaddr_vm *other)\n  54:\t{\n  55:\t\treturn addr-\u003esvm_cid == other-\u003esvm_cid \u0026\u0026\n  56:\t\t\taddr-\u003esvm_port == other-\u003esvm_port;\n  57:\t}\n  58:\tEXPORT_SYMBOL_GPL(vsock_addr_equals_addr);\n  59:\t\n  60:\tint vsock_addr_cast(const struct sockaddr_unsized *addr,\n  61:\t\t\t    size_t len, struct sockaddr_vm **out_addr)\n  62:\t{\n  63:\t\tif (len \u003c sizeof(**out_addr))\n  64:\t\t\treturn -EFAULT;\n"
}

29/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16297
							output: 45
							thoughts: 726
						

Thoughts:
**Analyzing the Virtio vsock Rx Batching Patch**

Alright, let's break this down. First, `vsock_addr_equals_addr` checks the `svm_cid` and `svm_port` – that's our baseline. Now, diving into the patch itself. The goal here is pretty clear: implement packet receive batching for virtio vsock.

Previously, `virtio_transport_rx_work` would process each incoming packet individually. This meant calling `virtio_transport_recv_pkt` for every single one. Each call acquired and released the socket lock (`lock_sock(sk)`), and triggered `sk_data_ready` and `sk_write_space` for every packet, which is inefficient.

This patch introduces `virtio_transport_rx_batch`. It groups a limited number of packets for the same socket under a *single* lock.  This delays the calls to `write_space` and `data_ready` until the batch completes in `virtio_transport_rx_batch_finish`. It also handles any necessary callback handoff if sockmap or BPF are in play (via `vsock_rx_callback_handoff`, and so on). That's a nice optimization.

Now, the real question: does this introduce any new risks or vulnerabilities? We need to look for uninitialized memory issues. Time to run through the "NeedsKMSAN=true" checklist.

1.  **Kernel structures sent to user space:** Does the patch expose anything to user space, potentially with uninitialized data? *No*, at first glance. This patch is solely concerned with kernel-internal rx batching and callback notifications for virtio vsock.

However, we need to be thorough. Let's check if the patch touches `virtio_transport_recv_enqueue` or any data copied to the user. We should inspect `virtio_transport_recv_connected` to see how SKB data is enqueued/read.

Looking at the code inside `virtio_transport_recv_connected`:
```c
 	switch (le16_to_cpu(hdr->op)) {
+		if (!data_ready_pending) {
+			vsock_data_ready(sk);
+		} else {
+			data_ready = READ_ONCE(sk->sk_data_ready);
+			if (data_ready == vsk->default_data_ready) {
+				if (!*data_ready_pending &&
+				    (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				     sock_flag(sk, SOCK_DONE)))
+					*data_ready_pending = true;
+			} else {
+				/* Use the callback seen for this packet. */
+				*data_ready_pending = false;
+				if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+				    sock_flag(sk, SOCK_DONE))
+					data_ready(sk);
+			}
+		}
```
Hold on, let's step back before that `switch` statement and see what's happening to the packet payload beforehand. Specifically, let's make certain that we're safe to proceed. Looking at `virtio_transport_recv_connected` in `net/vmw_vsock/virtio_transport_common.c` is the next step to confirm our assumptions are correct.



30/3 2026/10/10 14:35 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 1590,
  "LineCount": 40
}

Results:
{
  "Contents": "1590:\tstatic int\n1591:\tvirtio_transport_recv_connected(struct sock *sk,\n1592:\t\t\t\t\tstruct sk_buff *skb,\n1593:\t\t\t\t\tbool *data_ready_pending)\n1594:\t{\n1595:\t\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n1596:\t\tstruct vsock_sock *vsk = vsock_sk(sk);\n1597:\t\tvoid (*data_ready)(struct sock *sk);\n1598:\t\tint err = 0;\n1599:\t\n1600:\t\tswitch (le16_to_cpu(hdr-\u003eop)) {\n1601:\t\tcase VIRTIO_VSOCK_OP_RW:\n1602:\t\t\tif (!virtio_transport_recv_enqueue(vsk, skb)) {\n1603:\t\t\t\t/* There is no more space to queue the packet, so let's\n1604:\t\t\t\t * close the connection; otherwise, we'll lose data.\n1605:\t\t\t\t */\n1606:\t\t\t\t(void)virtio_transport_reset(vsk, skb);\n1607:\t\t\t\tvirtio_transport_do_close(vsk, true);\n1608:\t\t\t\tsk-\u003esk_err = ENOBUFS;\n1609:\t\t\t\tsk_error_report(sk);\n1610:\t\t\t\tvsock_remove_sock(vsk);\n1611:\t\t\t\tbreak;\n1612:\t\t\t}\n1613:\t\t\tif (!data_ready_pending) {\n1614:\t\t\t\tvsock_data_ready(sk);\n1615:\t\t\t} else {\n1616:\t\t\t\tdata_ready = READ_ONCE(sk-\u003esk_data_ready);\n1617:\t\t\t\tif (data_ready == vsk-\u003edefault_data_ready) {\n1618:\t\t\t\t\tif (!*data_ready_pending \u0026\u0026\n1619:\t\t\t\t\t    (vsock_stream_has_data(vsk) \u003e= sk-\u003esk_rcvlowat ||\n1620:\t\t\t\t\t     sock_flag(sk, SOCK_DONE)))\n1621:\t\t\t\t\t\t*data_ready_pending = true;\n1622:\t\t\t\t} else {\n1623:\t\t\t\t\t/* Use the callback seen for this packet. */\n1624:\t\t\t\t\t*data_ready_pending = false;\n1625:\t\t\t\t\tif (vsock_stream_has_data(vsk) \u003e= sk-\u003esk_rcvlowat ||\n1626:\t\t\t\t\t    sock_flag(sk, SOCK_DONE))\n1627:\t\t\t\t\t\tdata_ready(sk);\n1628:\t\t\t\t}\n1629:\t\t\t}\n"
}

31/3 2026/10/10 14:35 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18525
							output: 33
							thoughts: 2191
						

Thoughts:
**Analysis of Virtio-VSOCK RX Path for Uninitialized Variable Usage**

Alright, let's dissect this code with a fine-tooth comb, focusing on potential issues related to uninitialized variables. We're looking at the receive path of the virtio-vsock driver, and our goal is to ensure all variables used in conditional statements are properly initialized.

First, the function `virtio_transport_rx_skb_has_headroom`: This function's role is simple credit accounting for the RX queue, checking if we have enough buffer space. It's straightforward and doesn't introduce any variables that need our immediate scrutiny. The calculations used here are also fairly simple and use known quantities.

Next, we look into `virtio_transport_inc_rx_pkt`: This is about tracking the bytes for a given socket. It checks buffer allocation and has no variables of concern.

Now, we're diving into the meat of the RX path, starting with `virtio_transport_rx_work`. The key here is the `batch` variable, initialized with zero. This is a crucial detail! The C99 designated initializers in the context of C's zero initialization in `struct virtio_transport_rx_batch batch = {};`  guarantees that all fields within it are set to their default values (e.g., pointers to NULL, integers to 0, booleans to false). Therefore, any conditional logic or branching that relies on `batch.sk` is well-defined, and we can be certain that we are not reading from uninitialized memory.

Moving on to `virtio_transport_recv_pkt`, we see a series of local variables and a struct. `ctx` is initialized with designated initializers, `src` and `dst` are initialized with the `vsock_addr_init` function, which, fortunately, performs a `memset` to zero. This ensures that the addresses are initialized. The `free_pkt` is a boolean initialized by the result of the `virtio_transport_recv_pkt_locked` function. Any unmentioned struct members are initialized to 0/NULL due to the use of designated initializers, making the conditions that use this struct safe.

The function `virtio_transport_recv_pkt_batch` is up next. This is where things get a bit more interesting, and we must meticulously trace the flow of variables. We will analyze the different branches of the `if` and `else` statements within the function, ensuring that each local variable is assigned before it is used. `hdr` is immediately assigned, no issues there. The variables `batchable`, `defer_data_ready`, `start_batch`, and `free_pkt` are all initialized and/or assigned within the function, no issues. `src` and `dst` are, as we know, initialized by `virtio_transport_recv_pkt_init_addrs`, which uses `vsock_addr_init`, so they are memset to 0.

The variable `batchable` looks initially suspicious, and the code contains two branches depending on the status of `batch->sk` which is NULL when initialized. We carefully track its use. It's passed to `virtio_transport_recv_pkt_locked` via the `ctx` struct. We see that within `virtio_transport_recv_pkt_locked`, `batchable` is unconditionally initialized. We also observe that it is only used *after* it's been initialized to a known value, so there are no uninitialized reads of it. In the `lookup:` branch, `start_batch` is used to determine whether or not `batchable` will be initialized. If `start_batch` is false, `batchable` is never used. If `start_batch` is true, then `batchable` is conditionally passed to a function that initializes it, thereby guaranteeing that `batchable` is initialized before use.

Similarly, `start_batch` and `defer_data_ready` are initialized at the beginning of the `lookup` branch, and are assigned values and used in well-defined patterns. `free_pkt` is also initialized to return from the function `virtio_transport_recv_pkt_locked`. Finally, `src` and `dst` have known initializations, so no uninitialized reads here.

We can now conclude that our analysis has not identified any instances of potentially uninitialized variables being used in conditional logic or branching within the examined functions. This demonstrates that the code is structured in a way that minimizes the risk of such issues, and the use of zero initialization and designated initializers provides a strong foundation for correctness.



32/3 2026/10/10 14:36 tool grepper
0m Args:
{
  "Expression": "sk_alloc\\(",
  "PathPrefixes": [
    "net/vmw_vsock/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=1037=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1047-\nnet/vmw_vsock/af_vsock.c:1048:\tsk = sk_alloc(net, AF_VSOCK, priority, \u0026vsock_proto, kern);\nnet/vmw_vsock/af_vsock.c-1049-\tif (!sk)\n"
}

33/3 2026/10/10 14:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21879
							output: 35
							thoughts: 203
						

Thoughts:
**My Initial Thoughts on Socket Creation**

Okay, so `sk_alloc` is responsible for allocating memory, specifically zeroed memory. We're using `sk_prot_alloc`, so it's a `kzalloc` or `kmem_cache_zalloc` situation for our sockets. Got it. Now, in `__vsock_create`, the code grabs the `vsock_sock` from the socket (`sk`). It then stores the original `sk_data_ready` and `sk_write_space` in `vsk->default_data_ready` and `vsk->default_write_space` respectively and initializes a delayed work queue item `vsk->rx_cb_work` for our RX callback. The critical point is that `rx_cb_retried` is initially false because of the zeroing done during allocation. The code later sets `rx_cb_retried` explicitly in `vsock_rx_callback_handoff`.

But here's a wrinkle... what if a socket isn't created *exactly* this way? Are there other paths, other functions that might create a `struct vsock_sock`? I need to make sure I'm covering all the bases. Let me check functions like `vsock_create_connected` or something similar, and make sure that this initial state holds true in all cases. I can't assume this is the only path.



34/3 2026/10/10 14:36 tool grepper
0m Args:
{
  "Expression": "__vsock_create\\(",
  "PathPrefixes": [
    "net/vmw_vsock/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=1035=static void vsock_connect_timeout(struct work_struct *work);\nnet/vmw_vsock/af_vsock.c-1036-\nnet/vmw_vsock/af_vsock.c:1037:static struct sock *__vsock_create(struct net *net,\nnet/vmw_vsock/af_vsock.c-1038-\t\t\t\t   struct socket *sock,\n--\nnet/vmw_vsock/af_vsock.c=1180=struct sock *vsock_create_connected(struct sock *parent)\nnet/vmw_vsock/af_vsock.c-1181-{\nnet/vmw_vsock/af_vsock.c:1182:\treturn __vsock_create(sock_net(parent), NULL, parent, GFP_KERNEL,\nnet/vmw_vsock/af_vsock.c-1183-\t\t\t      parent-\u003esk_type, 0);\n--\nnet/vmw_vsock/af_vsock.c=2836=static int vsock_create(struct net *net, struct socket *sock,\n--\nnet/vmw_vsock/af_vsock.c-2864-\nnet/vmw_vsock/af_vsock.c:2865:\tsk = __vsock_create(net, sock, NULL, GFP_KERNEL, 0, kern);\nnet/vmw_vsock/af_vsock.c-2866-\tif (!sk)\n"
}

35/3 2026/10/10 14:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22822
							output: 43
							thoughts: 119
						

Thoughts:
Okay, here's my summary, taking the role of the expert:

**Deep Dive into vsock Socket Creation**

Right, let's dissect this `vsock` socket creation. The function `__vsock_create` is the *sole* function responsible for conjuring `struct vsock_sock` instances – both for entirely new sockets and those derived as children, which makes debugging easier in the long run. Good.

Now, a critical observation – within `__vsock_create`, the code sets `vsk->default_data_ready` to `sk->sk_data_ready` and `vsk->default_write_space` to `sk->sk_write_space`. That's a classic inheritance pattern. But that raises a question: when *are* `sk->sk_data_ready` and `sk->sk_write_space` actually initialized? It's essential to understand the parent's functions. Let's trace it down within `__vsock_create` and see where they're defined. We need to be sure the defaults are the correct values.



36/3 2026/10/10 14:36 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/af_vsock.c",
  "FirstLine": 1045,
  "LineCount": 40
}

Results:
{
  "Contents": "1045:\t\tstruct vsock_sock *psk;\n1046:\t\tstruct vsock_sock *vsk;\n1047:\t\n1048:\t\tsk = sk_alloc(net, AF_VSOCK, priority, \u0026vsock_proto, kern);\n1049:\t\tif (!sk)\n1050:\t\t\treturn NULL;\n1051:\t\n1052:\t\tsock_init_data(sock, sk);\n1053:\t\n1054:\t\t/* sk-\u003esk_type is normally set in sock_init_data, but only if sock is\n1055:\t\t * non-NULL. We make sure that our sockets always have a type by\n1056:\t\t * setting it here if needed.\n1057:\t\t */\n1058:\t\tif (!sock)\n1059:\t\t\tsk-\u003esk_type = type;\n1060:\t\n1061:\t\tvsk = vsock_sk(sk);\n1062:\t\tvsk-\u003edefault_data_ready = sk-\u003esk_data_ready;\n1063:\t\tvsk-\u003edefault_write_space = sk-\u003esk_write_space;\n1064:\t\tvsock_addr_init(\u0026vsk-\u003elocal_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n1065:\t\tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n1066:\t\n1067:\t\tsk-\u003esk_destruct = vsock_sk_destruct;\n1068:\t\tsk-\u003esk_backlog_rcv = vsock_queue_rcv_skb;\n1069:\t\tsock_reset_flag(sk, SOCK_DONE);\n1070:\t\n1071:\t\tINIT_LIST_HEAD(\u0026vsk-\u003ebound_table);\n1072:\t\tINIT_LIST_HEAD(\u0026vsk-\u003econnected_table);\n1073:\t\tvsk-\u003elistener = NULL;\n1074:\t\tINIT_LIST_HEAD(\u0026vsk-\u003epending_links);\n1075:\t\tINIT_LIST_HEAD(\u0026vsk-\u003eaccept_queue);\n1076:\t\tvsk-\u003esent_request = false;\n1077:\t\tvsk-\u003eignore_connecting_rst = false;\n1078:\t\tWRITE_ONCE(vsk-\u003epeer_shutdown, 0);\n1079:\t\tINIT_DELAYED_WORK(\u0026vsk-\u003econnect_work, vsock_connect_timeout);\n1080:\t\tINIT_DELAYED_WORK(\u0026vsk-\u003epending_work, vsock_pending_work);\n1081:\t\tINIT_DELAYED_WORK(\u0026vsk-\u003erx_cb_work, vsock_rx_callback_work);\n1082:\t\n1083:\t\tpsk = parent ? vsock_sk(parent) : NULL;\n1084:\t\tif (parent) {\n"
}

37/3 2026/10/10 14:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23957
							output: 21
							thoughts: 845
						

Thoughts:
**Analyzing vsock RX Callback Hand-off**

Alright, let's break this down. First, I see `sock_init_data` is setting up the socket's data-ready and write-space callbacks, specifically assigning `sock_def_readable` and `sock_def_write_space`. Then, the code's copying these default function pointers to the `vsk` structure, which I assume is a vsock-specific structure. Okay, standard initialization.

Now, the interesting part, `vsock_rx_callback_handoff`. I'm immediately noticing the checks for `SOCK_DEAD` and `vsk->transport`. Those are crucial. It looks like we're handling the hand-off of data reception and space availability notifications. There's a lock around the callback functions for concurrency.

The first check seems important: it determines if the protocol has changed. If the `sk_data_ready` function pointer hasn't been replaced from the default, it retries and then executes the default. That retry mechanism is interesting; it's likely dealing with potential race conditions during protocol swaps, which makes sense. I see a `vsk->rx_cb_retried` flag to prevent infinite loops.

The main loop then iterates up to `VSOCK_RX_CB_HANDOFF_MAX` times. Inside the loop, we have another lock/unlock pair, reading the callback function pointers. The code is carefully checking if `write_space` has been changed from its default value and calling it if it has; it's also executing `data_ready`. It has two breaks, the first if `data_ready` function pointer is the default, and the second if the data has been fully consumed.

The `vsock_stream_has_data` calls seem to be used to gauge the amount of data and whether there is any data to be read. There’s an interesting check comparing data before and after calls. The final conditional seems like an attempt to re-queue the callback if data is still available after the maximum number of attempts, which is an optimization for avoiding starvation.

So, in essence, this function appears to be a robust mechanism for ensuring the correct callbacks are triggered when vsock data becomes available, while also handling the nuances of protocol changes and ensuring resources are efficiently managed. I need to now verify if the socket destruction cancels this `rx_cb_work` in the `vsock_sk_destruct` function.



38/3 2026/10/10 14:36 tool grepper
0m Args:
{
  "Expression": "vsock_sk_destruct"
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=176=static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr);\nnet/vmw_vsock/af_vsock.c:177:static void vsock_sk_destruct(struct sock *sk);\nnet/vmw_vsock/af_vsock.c-178-static int vsock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);\n--\nnet/vmw_vsock/af_vsock.c=1037=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1066-\nnet/vmw_vsock/af_vsock.c:1067:\tsk-\u003esk_destruct = vsock_sk_destruct;\nnet/vmw_vsock/af_vsock.c-1068-\tsk-\u003esk_backlog_rcv = vsock_queue_rcv_skb;\n--\nnet/vmw_vsock/af_vsock.c=1109=static void __vsock_release(struct sock *sk, int level)\n--\nnet/vmw_vsock/af_vsock.c-1150-\nnet/vmw_vsock/af_vsock.c:1151:static void vsock_sk_destruct(struct sock *sk)\nnet/vmw_vsock/af_vsock.c-1152-{\n"
}

39/3 2026/10/10 14:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25737
							output: 43
							thoughts: 21
						

Thoughts:
Okay, let's dive into these functions. I need to understand how vsock sockets are torn down, so `vsock_sk_destruct` and `__vsock_release` are my targets. I'm already familiar with socket lifecycle in general, so I'm looking for the specific vsock nuances. I'm expecting `vsock_sk_destruct` to handle the final cleanup – releasing resources, freeing memory associated with the socket structure itself, and perhaps deregistering the socket from any internal lists or data structures. I'll need to pay close attention to the order of operations here; it's critical to avoid dangling pointers or memory leaks during the teardown process.

Then, `__vsock_release` likely coordinates the higher-level release actions, potentially called from a socket close, shutdown or other related user-level system calls. I'm anticipating that this function will handle things like notifying the peer (if necessary), maybe flushing any pending data, and decrementing reference counts before ultimately invoking `vsock_sk_destruct` or similar functionality. I need to observe how the control flows between these two functions and how they relate to the various states a vsock socket can be in. Specifically, I'll need to figure out what data-structures are freed and what protection mechanisms, such as locks, are used to prevent races, as well as ensure the integrity of the release path. I'm keen to see if any specific vsock-related flags or states are managed during these operations, as that would give me further insight into the vsock kernel module's design choices.



40/3 2026/10/10 14:37 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/af_vsock.c",
  "FirstLine": 1140,
  "LineCount": 40
}

Results:
{
  "Contents": "1140:\t\n1141:\t\t/* Clean up any sockets that never were accepted. */\n1142:\t\twhile ((pending = vsock_dequeue_accept(sk)) != NULL) {\n1143:\t\t\t__vsock_release(pending, SINGLE_DEPTH_NESTING);\n1144:\t\t\tsock_put(pending);\n1145:\t\t}\n1146:\t\n1147:\t\trelease_sock(sk);\n1148:\t\tsock_put(sk);\n1149:\t}\n1150:\t\n1151:\tstatic void vsock_sk_destruct(struct sock *sk)\n1152:\t{\n1153:\t\tstruct vsock_sock *vsk = vsock_sk(sk);\n1154:\t\n1155:\t\t/* Flush MSG_ZEROCOPY leftovers. */\n1156:\t\t__skb_queue_purge(\u0026sk-\u003esk_error_queue);\n1157:\t\n1158:\t\tvsock_deassign_transport(vsk);\n1159:\t\n1160:\t\t/* When clearing these addresses, there's no need to set the family and\n1161:\t\t * possibly register the address family with the kernel.\n1162:\t\t */\n1163:\t\tvsock_addr_init(\u0026vsk-\u003elocal_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n1164:\t\tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\n1165:\t\n1166:\t\tput_cred(vsk-\u003eowner);\n1167:\t}\n1168:\t\n1169:\tstatic int vsock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)\n1170:\t{\n1171:\t\tint err;\n1172:\t\n1173:\t\terr = sock_queue_rcv_skb(sk, skb);\n1174:\t\tif (err)\n1175:\t\t\tkfree_skb(skb);\n1176:\t\n1177:\t\treturn err;\n1178:\t}\n1179:\t\n"
}

41/3 2026/10/10 14:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26718
							output: 18
							thoughts: 409
						

Thoughts:
**Analyzing the vsock RX Callback Work Queue**

Okay, let's dissect the lifecycle of `rx_cb_work` within the vsock implementation. First, I see how `vsock_rx_callback_queue` in `vsock_rx_callback_queue` manages the scheduling. Crucially, `sock_hold(sk)` is called *before* scheduling the delayed work. This is important, as it ensures that the socket (`sk`) isn't prematurely freed while the work item is queued and potentially still active.

Then, when the work item `rx_cb_work` executes in `vsock_rx_callback_work`, we have a corresponding `sock_put(sk)` call at the end, along with a `release_sock(sk)`. This all seems correct and balanced. The lock ensures we have a proper synchronization when accessing the socket, and there's a check for `SOCK_DEAD` to prevent action on a socket that is being destructed.

If there were a lifetime issue, I'd expect to see either a Use-After-Free (UAF) situation or a memory leak. Given the `sock_hold` and `sock_put` calls, along with the `sock_orphan` in `__vsock_release`, it seems this is preventing any undefined behavior. These would likely be flagged by KASAN or KMEMLEAK, so that's a positive sign.

Now, let's perform a thorough code review. It's time to broaden the investigation. I need to search the entire codebase for any other accesses to `rx_cb_` or any related new fields to be absolutely certain there are no hidden issues. I'll need to confirm that this is the only call site, as that will ensure the entire lifetime of the `rx_cb_work` field is known.



42/3 2026/10/10 14:37 tool grepper
0m Args:
{
  "Expression": "rx_cb_"
}

Results:
{
  "Output": "Documentation/staging/rpmsg.rst=149=Returns 0 on success and an appropriate error value on failure.\n--\nDocumentation/staging/rpmsg.rst-153-  struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,\nDocumentation/staging/rpmsg.rst:154:\t\t\t\t\t  rpmsg_rx_cb_t cb, void *priv,\nDocumentation/staging/rpmsg.rst-155-\t\t\t\t\t  struct rpmsg_channel_info chinfo);\n--\ndrivers/media/pci/ngene/ngene.h=691=struct ngene;\ndrivers/media/pci/ngene/ngene.h-692-\ndrivers/media/pci/ngene/ngene.h:693:typedef void (rx_cb_t)(struct ngene *, u32, u8);\ndrivers/media/pci/ngene/ngene.h-694-typedef void (tx_cb_t)(struct ngene *, u32);\n--\ndrivers/media/pci/ngene/ngene.h=696=struct ngene {\n--\ndrivers/media/pci/ngene/ngene.h-742-\ttx_cb_t              *TxEventNotify;\ndrivers/media/pci/ngene/ngene.h:743:\trx_cb_t              *RxEventNotify;\ndrivers/media/pci/ngene/ngene.h-744-\tint                   tx_busy;\n--\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c=689=static void\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c:690:bna_rx_cb_rxf_started(struct bna_rx *rx)\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-691-{\n--\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c=696=bna_rxf_start(struct bna_rxf *rxf)\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-697-{\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c:698:\trxf-\u003estart_cbfn = bna_rx_cb_rxf_started;\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-699-\trxf-\u003estart_cbarg = rxf-\u003erx;\n--\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c=703=static void\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c:704:bna_rx_cb_rxf_stopped(struct bna_rx *rx)\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-705-{\n--\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c=710=bna_rxf_stop(struct bna_rxf *rxf)\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-711-{\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c:712:\trxf-\u003estop_cbfn = bna_rx_cb_rxf_stopped;\ndrivers/net/ethernet/brocade/bna/bna_tx_rx.c-713-\trxf-\u003estop_cbarg = rxf-\u003erx;\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c=53=static struct ieee80211_channel wil_60ghz_channels[] = {\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c-60-/* Rx channel bonding mode */\ndrivers/net/wireless/ath/wil6210/cfg80211.c:61:enum wil_rx_cb_mode {\ndrivers/net/wireless/ath/wil6210/cfg80211.c-62-\tWIL_RX_CB_MODE_DMG,\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c-66-\ndrivers/net/wireless/ath/wil6210/cfg80211.c:67:static int wil_rx_cb_mode_to_n_bonded(u8 cb_mode)\ndrivers/net/wireless/ath/wil6210/cfg80211.c-68-{\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c=431=int wil_cid_fill_sinfo(struct wil6210_vif *vif, int cid,\n--\ndrivers/net/wireless/ath/wil6210/cfg80211.c-515-\tsinfo-\u003erxrate.n_bonded_ch =\ndrivers/net/wireless/ath/wil6210/cfg80211.c:516:\t\t\t     wil_rx_cb_mode_to_n_bonded(stats-\u003elast_cb_mode_rx);\ndrivers/net/wireless/ath/wil6210/cfg80211.c-517-\tsinfo-\u003erx_bytes = stats-\u003erx_bytes;\n--\ndrivers/rpmsg/mtk_rpmsg.c=85=__mtk_create_ept(struct mtk_rpmsg_rproc_subdev *mtk_subdev,\ndrivers/rpmsg/mtk_rpmsg.c:86:\t\t struct rpmsg_device *rpdev, rpmsg_rx_cb_t cb, void *priv,\ndrivers/rpmsg/mtk_rpmsg.c-87-\t\t u32 id)\n--\ndrivers/rpmsg/mtk_rpmsg.c=119=static struct rpmsg_endpoint *\ndrivers/rpmsg/mtk_rpmsg.c:120:mtk_rpmsg_create_ept(struct rpmsg_device *rpdev, rpmsg_rx_cb_t cb, void *priv,\ndrivers/rpmsg/mtk_rpmsg.c-121-\t\t     struct rpmsg_channel_info chinfo)\n--\ndrivers/rpmsg/qcom_glink_native.c=1319=static struct rpmsg_endpoint *qcom_glink_create_ept(struct rpmsg_device *rpdev,\ndrivers/rpmsg/qcom_glink_native.c:1320:\t\t\t\t\t\t    rpmsg_rx_cb_t cb,\ndrivers/rpmsg/qcom_glink_native.c-1321-\t\t\t\t\t\t    void *priv,\n--\ndrivers/rpmsg/qcom_smd.c=413=static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,\ndrivers/rpmsg/qcom_smd.c:414:\t\t\t\t\t  rpmsg_rx_cb_t cb)\ndrivers/rpmsg/qcom_smd.c-415-{\n--\ndrivers/rpmsg/qcom_smd.c=815=static int qcom_smd_channel_open(struct qcom_smd_channel *channel,\ndrivers/rpmsg/qcom_smd.c:816:\t\t\t\t rpmsg_rx_cb_t cb)\ndrivers/rpmsg/qcom_smd.c-817-{\n--\ndrivers/rpmsg/qcom_smd.c=901=static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev,\ndrivers/rpmsg/qcom_smd.c:902:\t\t\t\t\t\t  rpmsg_rx_cb_t cb, void *priv,\ndrivers/rpmsg/qcom_smd.c-903-\t\t\t\t\t\t  struct rpmsg_channel_info chinfo)\n--\ndrivers/rpmsg/rpmsg_core.c=112=struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,\ndrivers/rpmsg/rpmsg_core.c:113:\t\t\t\t\trpmsg_rx_cb_t cb, void *priv,\ndrivers/rpmsg/rpmsg_core.c-114-\t\t\t\t\tstruct rpmsg_channel_info chinfo)\n--\ndrivers/rpmsg/rpmsg_internal.h=35=struct rpmsg_device_ops {\n--\ndrivers/rpmsg/rpmsg_internal.h-40-\tstruct rpmsg_endpoint *(*create_ept)(struct rpmsg_device *rpdev,\ndrivers/rpmsg/rpmsg_internal.h:41:\t\t\t\t\t    rpmsg_rx_cb_t cb, void *priv,\ndrivers/rpmsg/rpmsg_internal.h-42-\t\t\t\t\t    struct rpmsg_channel_info chinfo);\n--\ndrivers/rpmsg/virtio_rpmsg_bus.c=205=static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,\ndrivers/rpmsg/virtio_rpmsg_bus.c-206-\t\t\t\t\t\t struct rpmsg_device *rpdev,\ndrivers/rpmsg/virtio_rpmsg_bus.c:207:\t\t\t\t\t\t rpmsg_rx_cb_t cb,\ndrivers/rpmsg/virtio_rpmsg_bus.c-208-\t\t\t\t\t\t void *priv, u32 addr)\n--\ndrivers/rpmsg/virtio_rpmsg_bus.c=273=static struct rpmsg_endpoint *virtio_rpmsg_create_ept(struct rpmsg_device *rpdev,\ndrivers/rpmsg/virtio_rpmsg_bus.c:274:\t\t\t\t\t\t      rpmsg_rx_cb_t cb,\ndrivers/rpmsg/virtio_rpmsg_bus.c-275-\t\t\t\t\t\t      void *priv,\n--\ndrivers/soc/qcom/wcnss_ctrl.c=200=static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc)\n--\ndrivers/soc/qcom/wcnss_ctrl.c-281- */\ndrivers/soc/qcom/wcnss_ctrl.c:282:struct rpmsg_endpoint *qcom_wcnss_open_channel(void *wcnss, const char *name, rpmsg_rx_cb_t cb, void *priv)\ndrivers/soc/qcom/wcnss_ctrl.c-283-{\n--\ninclude/linux/rpmsg.h=50=struct rpmsg_device {\n--\ninclude/linux/rpmsg.h-61-\ninclude/linux/rpmsg.h:62:typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32);\ninclude/linux/rpmsg.h-63-typedef int (*rpmsg_flowcontrol_cb_t)(struct rpmsg_device *, void *, bool);\n--\ninclude/linux/rpmsg.h=89=struct rpmsg_endpoint {\n--\ninclude/linux/rpmsg.h-91-\tstruct kref refcount;\ninclude/linux/rpmsg.h:92:\trpmsg_rx_cb_t cb;\ninclude/linux/rpmsg.h-93-\trpmsg_flowcontrol_cb_t flow_cb;\n--\ninclude/linux/rpmsg.h=177=struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *,\ninclude/linux/rpmsg.h:178:\t\t\t\t\trpmsg_rx_cb_t cb, void *priv,\ninclude/linux/rpmsg.h-179-\t\t\t\t\tstruct rpmsg_channel_info chinfo);\n--\ninclude/linux/rpmsg.h=237=static inline struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,\ninclude/linux/rpmsg.h:238:\t\t\t\t\t\t      rpmsg_rx_cb_t cb,\ninclude/linux/rpmsg.h-239-\t\t\t\t\t\t      void *priv,\n--\ninclude/linux/soc/qcom/wcnss_ctrl.h=9=struct rpmsg_endpoint *qcom_wcnss_open_channel(void *wcnss, const char *name,\ninclude/linux/soc/qcom/wcnss_ctrl.h:10:\t\t\t\t\t       rpmsg_rx_cb_t cb, void *priv);\ninclude/linux/soc/qcom/wcnss_ctrl.h-11-\n--\ninclude/linux/soc/qcom/wcnss_ctrl.h=14=static struct rpmsg_endpoint *qcom_wcnss_open_channel(void *wcnss,\ninclude/linux/soc/qcom/wcnss_ctrl.h-15-\t\t\t\t\t\t      const char *name,\ninclude/linux/soc/qcom/wcnss_ctrl.h:16:\t\t\t\t\t\t      rpmsg_rx_cb_t cb,\ninclude/linux/soc/qcom/wcnss_ctrl.h-17-\t\t\t\t\t\t      void *priv)\n--\ninclude/net/af_vsock.h=29=struct vsock_sock {\n--\ninclude/net/af_vsock.h-68-\tvoid (*default_write_space)(struct sock *sk);\ninclude/net/af_vsock.h:69:\tstruct delayed_work rx_cb_work;\ninclude/net/af_vsock.h:70:\tbool rx_cb_retried;\ninclude/net/af_vsock.h-71-\n--\ninclude/net/af_vsock.h=85=void vsock_data_ready(struct sock *sk);\ninclude/net/af_vsock.h:86:bool vsock_rx_cb_is_native(struct sock *sk);\ninclude/net/af_vsock.h-87-void vsock_rx_callback_kick(struct sock *sk);\n--\nnet/vmw_vsock/af_vsock.c=936=static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)\n--\nnet/vmw_vsock/af_vsock.c-940-\tsock_hold(sk);\nnet/vmw_vsock/af_vsock.c:941:\tif (!schedule_delayed_work(\u0026vsk-\u003erx_cb_work, delay))\nnet/vmw_vsock/af_vsock.c-942-\t\tsock_put(sk);\n--\nnet/vmw_vsock/af_vsock.c=945=void vsock_rx_callback_kick(struct sock *sk)\n--\nnet/vmw_vsock/af_vsock.c-950-/* Caller holds sk_callback_lock. */\nnet/vmw_vsock/af_vsock.c:951:bool vsock_rx_cb_is_native(struct sock *sk)\nnet/vmw_vsock/af_vsock.c-952-{\n--\nnet/vmw_vsock/af_vsock.c-958-}\nnet/vmw_vsock/af_vsock.c:959:EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);\nnet/vmw_vsock/af_vsock.c-960-\n--\nnet/vmw_vsock/af_vsock.c=962=void vsock_rx_callback_handoff(struct sock *sk)\n--\nnet/vmw_vsock/af_vsock.c-982-\t\t */\nnet/vmw_vsock/af_vsock.c:983:\t\tif (!vsk-\u003erx_cb_retried) {\nnet/vmw_vsock/af_vsock.c:984:\t\t\tvsk-\u003erx_cb_retried = true;\nnet/vmw_vsock/af_vsock.c-985-\t\t\tvsock_rx_callback_queue(sk, 1);\n--\nnet/vmw_vsock/af_vsock.c-987-\t\t}\nnet/vmw_vsock/af_vsock.c:988:\t\tvsk-\u003erx_cb_retried = false;\nnet/vmw_vsock/af_vsock.c-989-\t\tdata_ready(sk);\n--\nnet/vmw_vsock/af_vsock.c-992-\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\nnet/vmw_vsock/af_vsock.c:993:\tvsk-\u003erx_cb_retried = false;\nnet/vmw_vsock/af_vsock.c-994-\n--\nnet/vmw_vsock/af_vsock.c=1022=static void vsock_rx_callback_work(struct work_struct *work)\n--\nnet/vmw_vsock/af_vsock.c-1024-\tstruct vsock_sock *vsk = container_of(work, struct vsock_sock,\nnet/vmw_vsock/af_vsock.c:1025:\t\t\t\t\t      rx_cb_work.work);\nnet/vmw_vsock/af_vsock.c-1026-\tstruct sock *sk = sk_vsock(vsk);\n--\nnet/vmw_vsock/af_vsock.c=1037=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1080-\tINIT_DELAYED_WORK(\u0026vsk-\u003epending_work, vsock_pending_work);\nnet/vmw_vsock/af_vsock.c:1081:\tINIT_DELAYED_WORK(\u0026vsk-\u003erx_cb_work, vsock_rx_callback_work);\nnet/vmw_vsock/af_vsock.c-1082-\n--\nnet/vmw_vsock/virtio_transport_common.c=2012=void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)\n--\nnet/vmw_vsock/virtio_transport_common.c-2034-\tread_lock_bh(\u0026sk-\u003esk_callback_lock);\nnet/vmw_vsock/virtio_transport_common.c:2035:\tnative = vsock_rx_cb_is_native(sk);\nnet/vmw_vsock/virtio_transport_common.c-2036-\tread_unlock_bh(\u0026sk-\u003esk_callback_lock);\n"
}

43/3 2026/10/10 14:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32142
							output: 36
							thoughts: 66
						

Thoughts:
**Investigating Cancellation of Asynchronous Tasks**

Okay, let's dive into this code. My immediate concern is ensuring proper cleanup of asynchronous tasks, specifically related to this `vsk` structure. I need to verify where `cancel_delayed_work_sync(&vsk->rx_cb_work)` is actually invoked within the codebase. I want to make sure it's called reliably, ideally in scenarios where the associated work is no longer needed.

Hmm, wait a minute, I also need to consider the broader picture of work cancellation. Aside from `rx_cb_work`, I should also check how we handle `connect_work` and `pending_work`. These seem like equally important asynchronous operations that require careful cancellation logic. I can't assume a uniform cancellation policy across all work items.

Alright, time to get a little more systematic. I'll need to use `grep` to quickly identify all instances where `connect_work` and `pending_work` are referenced within the project. This should reveal the cancellation points and give me a clear overview of the current state of asynchronous task management within the system, especially to see if there is any overlap in the cancellations of the different work types. This should paint a good picture of the potential issues and any areas of concern.



44/3 2026/10/10 14:37 tool grepper
0m Args:
{
  "Expression": "connect_work|pending_work",
  "PathPrefixes": [
    "net/vmw_vsock/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=779=static int vsock_send_shutdown(struct sock *sk, int mode)\n--\nnet/vmw_vsock/af_vsock.c-788-\nnet/vmw_vsock/af_vsock.c:789:static void vsock_pending_work(struct work_struct *work)\nnet/vmw_vsock/af_vsock.c-790-{\n--\nnet/vmw_vsock/af_vsock.c-795-\nnet/vmw_vsock/af_vsock.c:796:\tvsk = container_of(work, struct vsock_sock, pending_work.work);\nnet/vmw_vsock/af_vsock.c-797-\tsk = sk_vsock(vsk);\n--\nnet/vmw_vsock/af_vsock.c=1037=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1078-\tWRITE_ONCE(vsk-\u003epeer_shutdown, 0);\nnet/vmw_vsock/af_vsock.c:1079:\tINIT_DELAYED_WORK(\u0026vsk-\u003econnect_work, vsock_connect_timeout);\nnet/vmw_vsock/af_vsock.c:1080:\tINIT_DELAYED_WORK(\u0026vsk-\u003epending_work, vsock_pending_work);\nnet/vmw_vsock/af_vsock.c-1081-\tINIT_DELAYED_WORK(\u0026vsk-\u003erx_cb_work, vsock_rx_callback_work);\n--\nnet/vmw_vsock/af_vsock.c=1786=static void vsock_connect_timeout(struct work_struct *work)\n--\nnet/vmw_vsock/af_vsock.c-1790-\nnet/vmw_vsock/af_vsock.c:1791:\tvsk = container_of(work, struct vsock_sock, connect_work.work);\nnet/vmw_vsock/af_vsock.c-1792-\tsk = sk_vsock(vsk);\n--\nnet/vmw_vsock/af_vsock.c=1808=static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,\n--\nnet/vmw_vsock/af_vsock.c-1928-\t\t\t */\nnet/vmw_vsock/af_vsock.c:1929:\t\t\tif (mod_delayed_work(system_percpu_wq, \u0026vsk-\u003econnect_work,\nnet/vmw_vsock/af_vsock.c-1930-\t\t\t\t\t     timeout))\n--\nnet/vmw_vsock/vmci_transport.c=970=static int vmci_transport_recv_listen(struct sock *sk,\n--\nnet/vmw_vsock/vmci_transport.c-1151-\tsock_hold(pending);\nnet/vmw_vsock/vmci_transport.c:1152:\tschedule_delayed_work(\u0026vpending-\u003epending_work, HZ);\nnet/vmw_vsock/vmci_transport.c-1153-\n"
}

45/3 2026/10/10 14:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33346
							output: 380
							thoughts: 2371
						

Thoughts:
**KMSAN Evaluation: No Uninitialized Memory Risks**

Okay, let's break this down. My initial assessment focuses on the potential for memory safety issues introduced by this patch, specifically the risk of uninitialized memory usage. I'm seeing `sock_hold(sk)` before scheduling and `sock_put(sk)` in the work function, so if there's a problem here, my gut says it'll be around refcounting, use-after-free, or a leak—which KASAN should readily catch.

My primary concern is whether this patch changes how data moves between kernel space and user space. I immediately check `virtio_transport.c` for any changes to packet parsing or data unmarshaling. I see the usual suspects: `virtio_vsock_hdr(skb)` and the `payload_len` handling. But this code is *unchanged*, meaning the patch isn't messing with how data gets read or written, or copied. The batching is purely an optimization of socket locking and deferred notifications. It has *zero* impact on `recvmsg` or data handling.

I then dig into the specifics, especially the `struct virtio_transport_rx_batch`. I need to ensure that it or its fields don't accidentally leak to user space. It is defined in `include/linux/virtio_vsock.h`. My analysis confirms it's local to `virtio_transport.c` and is stack-allocated and *completely* zero-initialized. It's never passed to userspace. That's a huge relief.

Next, I need to look at any potential information leaks. `trace_virtio_transport_recv_pkt` is a prime candidate. Here, I trace `src` and `dst` addresses, which are initialized by `virtio_transport_recv_pkt_init_addrs`, and `vsock_addr_init`, which memsets the structures to zero before assignment. This logic remains unchanged from before the patch.

I also need to see if `vsock_bpf.c` is touched. I see a change adding `vsock_rx_callback_kick(sk)`, but this simply queues a work item to run `vsock_rx_callback_handoff(sk)`. This looks like a notification mechanism, and not a data source.

Now, the crucial question: are there any risks of uninitialized memory? I meticulously review the rules for KMSAN usage. I systematically examine the patch against each point:

1.  Kernel structures sent to user space: *Nope.* There's no use of `copy_to_user`, `put_user`, netlink skb attributes, ioctl output, socket options, or BPF buffers to send anything to user space.
2.  Conditional logic with uninitialized variables: *No.* All the variables and structures I've looked at (`batch`, `ctx`, `src`, `dst`, etc.) are explicitly initialized before use, either through designated initializers, `memset`, or explicit assignment.
3.  Complex data structures with uninitialized fields:  `struct virtio_transport_rx_batch` is stack-allocated and zero-initialized. The same is true for the `struct vsock_sock`.
4.  Bounds checks or logic that allow access to uninitialized bytes:  The patch is about RX packet batching and callback notifications, and under the socket lock. There's no modification of payload bounds or length calculations that might expose uninitialized buffer memory.

My conclusion is that this patch doesn't introduce any uninitialized memory risks. The key risk would seem to be concurrency, locking, or memory-management bugs, and this is caught by LOCKDEP, KASAN, or refcount checkers, *not* KMSAN.

Therefore, `NeedsKMSAN=false`.

In summary, this patch introduces RX packet batching, defers socket notifications, and refactors some socket lookup and address initialization logic. The code is carefully structured to avoid introducing uninitialized memory risks. All of the new variables and structures are properly initialized or zeroed before use, and no data is copied to userspace.



46/3 2026/10/10 14:38 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series implements receive packet batching and deferred callback handling for virtio-vsock to optimize locking overhead across sequences of STREAM/RW packets.\n\nEvaluation of KMSAN vs KASAN/LOCKDEP applicability:\n1. Memory initialization: The new stack-allocated batch tracking structure (`struct virtio_transport_rx_batch`) is zero-initialized (`batch = {}`). New socket fields in `struct vsock_sock` (`default_data_ready`, `default_write_space`, `rx_cb_work`, `rx_cb_retried`) are allocated via `sk_alloc` (which zeroes memory) and explicitly initialized in `__vsock_create`. Address structures (`src`, `dst`) are initialized via `vsock_addr_init`, which zeroes the entire `struct sockaddr_vm` via `memset`. Context structures and boolean flags are fully assigned before being read or used in conditional branches.\n2. Info-leaks / User-space copies: The changes do not introduce or modify any data transfers to user space (`copy_to_user`, ioctl, netlink, or socket options).\n3. Buffer bounds / uninitialized buffer reads: The batching logic does not alter buffer slicing or payload boundary checks in a way that could expose uninitialized memory.\n4. Bug profile: The primary failure modes of this patch involve socket reference counting, socket/callback lock ordering, race conditions with BPF/sockmap callback changes, or use-after-free conditions on socket or sk_buff lifetimes. These are comprehensively addressed by KASAN, LOCKDEP, and standard kernel debugging facilities, not KMSAN.\n\nTherefore, running a dedicated KMSAN session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series implements receive packet batching and deferred callback handling for virtio-vsock to optimize locking overhead across sequences of STREAM/RW packets.\n\nEvaluation of KMSAN vs KASAN/LOCKDEP applicability:\n1. Memory initialization: The new stack-allocated batch tracking structure (`struct virtio_transport_rx_batch`) is zero-initialized (`batch = {}`). New socket fields in `struct vsock_sock` (`default_data_ready`, `default_write_space`, `rx_cb_work`, `rx_cb_retried`) are allocated via `sk_alloc` (which zeroes memory) and explicitly initialized in `__vsock_create`. Address structures (`src`, `dst`) are initialized via `vsock_addr_init`, which zeroes the entire `struct sockaddr_vm` via `memset`. Context structures and boolean flags are fully assigned before being read or used in conditional branches.\n2. Info-leaks / User-space copies: The changes do not introduce or modify any data transfers to user space (`copy_to_user`, ioctl, netlink, or socket options).\n3. Buffer bounds / uninitialized buffer reads: The batching logic does not alter buffer slicing or payload boundary checks in a way that could expose uninitialized memory.\n4. Bug profile: The primary failure modes of this patch involve socket reference counting, socket/callback lock ordering, race conditions with BPF/sockmap callback changes, or use-after-free conditions on socket or sk_buff lifetimes. These are comprehensively addressed by KASAN, LOCKDEP, and standard kernel debugging facilities, not KMSAN.\n\nTherefore, running a dedicated KMSAN session is not warranted.",
  "NeedsKMSAN": false
}

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)